Почему компилятор С# разрешает пустые перечисления?

Я случайно определил перечисление сегодня, в котором не было значений. Например, например:

public enum MyConfusingEnum{}

Компилятор был очень рад дать мне определить, что и код успешно создан.

Теперь я, очевидно, не могу использовать это в традиционном смысле, так как код...

var mySadCompiler = MyConfusingEnum;

не указывает значение, но достаточно интересно, что я был способен сказать.

var myRoundTheHousesZeroState = Activator.CreateInstance<MyConfusingEnum>();

который, как я уже упоминал, является типом значения MyConfusingEnum со значением 0;

Мой вопрос: почему компилятор разрешает пустое определение и существуют ли какие-либо сценарии, где это может быть полезно?

Ответ 1

Во-первых, вы могли бы сделать это гораздо проще:

MyConfusingEnum x1 = 0;
MyConfusingEnum x2 = default(MyConfusingEnum);
MyConfusingEnum x3 = new MyConfusingEnum();
MyConfusingEnum x4 = (MyConfusingEnum) 123;

все вышеизложенное работает просто отлично. (Вы можете быть удивлены, что первый работает, см. Раздел спецификации о неявных преобразованиях перечислений для деталей.)

Мой вопрос в том, почему компилятор разрешает пустое определение

Начну с ответа на вопрос с вопросом. Не могли бы вы также отказаться от компилятора?

class C {}
interface I {}
struct S {}

Почему или почему?

Чтобы более прямо не ответить на ваш вопрос: "Почему мир не отличается от него?" вопросы трудно ответить. Вместо того, чтобы ответить на этот нерешенный вопрос, я отвечу на вопрос "предположим, что для пустых перечислений была допущена ошибка в команде дизайнеров, как бы вы ответили на этот шаг?" Этот вопрос по-прежнему противоречив, но, по крайней мере, это тот, на который я могу ответить.

Затем возникает вопрос, оправдывается ли стоимость этой функции ее преимуществами.

Для того, чтобы работать на языке, необходимо учитывать, конструировать, указывать, проверять, тестировать, документировать и отправлять клиентам. Это функция "произвести ошибку", поэтому сообщение об ошибке должно быть записано и переведено на несколько десятков языков, как и документация. Пять минут, которые потребовалось бы мне для реализации этой функции, перешли на многие часы работы многих людей, которым платят довольно много.

Однако на самом деле это не значительная стоимость. альтернативная стоимость - это соответствующая стоимость. Бюджеты конечны, функции не являются бесплатными, и поэтому любая реализованная функция означает, что нужно отрезать другую функцию; какую функцию С# вы хотели бы сократить, чтобы получить эту функцию? Утраченная выгода от невозможности сделать лучшую функцию - это альтернативные издержки.

Я также отмечаю, что ваша предлагаемая функция имеет нулевую очевидную выгоду для всех, что сделало бы ее жесткой продажей проектной комиссии. Возможно, есть неотразимая польза, которую я не вижу; если да, то что это?

Существуют ли какие-либо сценарии, где это может быть полезно?

Никто не приходит на ум. "Отказ от программ, которые явно не полезны" - это не цель разработки С#.

Ответ 2

Вы можете указать любое значение базового целочисленного типа (по-моему, int по умолчанию) для перечисления - поэтому (MyConfusingEnum)42 теперь будет иметь этот тип перечисления.

Я не думаю, что это хорошая идея в целом, но могут быть случаи, когда значения "enum" поступают из внешнего источника, а код просто выглядит лучше с помощью enum.

Пример (предполагая, что код инкапсулирует некоторое "основанное на int состояние" в Enum:

enum ExternalDeviceState {};

ExternalDeviceState GetState(){ ... return (ExternalDeviceState )intState;}
bool IsDeviceStillOk(ExternalDeviceState currentState) { .... }

Спецификация действительно разрешает пустое перечисление:

14.1 Объявления перечисления

Объявление enum объявляет новый тип перечисления. Объявление enum начинается с ключевого слова enum и определяет имя, доступность, базовый тип и члены перечисления.

enum-declaration:
   attributesopt   enum-modifiersopt   enum   identifier
        enum-base(opt)   enum-body   ;(opt)

enum-base:
:   integral-type

enum-body:
  {   enum-member-declarations(opt)   }  
  {   enum-member-declarations   ,   }

Обратите внимание, что enum-member-declarations(opt) явно помечен как вариант, где ничего не находится внутри {}.

Ответ 3

Activator.CreateInstance<MyConfusingEnum>(); совпадает с new MyConfusingEnum(). (docs)

Вызов конструктора перечисления дает вам 0 как значение.

Из-за конструктивного решения перечисление может иметь любое значение, которое является допустимым для типа поддержки (обычно int), оно не должно быть значением, определенным в перечислении.

По причине этого дизайнерского решения я могу указать этот ответ на вопрос под названием "Почему кастинг int недействителен для значения перечисления NOT throw exception?"

@AlexeiLevenkov предоставил спецификацию, которая допускает пустое перечисление, мы можем догадаться, что для этого является обоснованием того, что, поскольку значение типа поддержки поддерживается, допускается пустое перечисление.

Ответ 4

Существуют ли какие-либо сценарии, где это может быть полезно?

Как уже упоминалось, вы можете присвоить этому перечислению любое значение, которое разрешает базовый тип простым приведением. Таким образом, вы можете принудительно ввести проверку типов, в ситуациях, когда int может сбить с толку. Например:

public enum Argb : int {}

public void SetColor(Argb a) { ....

или вы хотите иметь некоторые методы расширения без cluping типа данных int, с ними

public static Color GetColor(this Argb value) {
    return new Color( (int)value );
}

public static void Deconstruct( this Argb color, out byte alpha, out byte red, out byte green, out byte blue ) {
        alpha = (byte)( (uint)color >> 24 );
        red = (byte)( (uint)color >> 16 );
        green = (byte)( (uint)color >> 8 );
        blue = (byte)color;
}

и использовать его как

var (alpha, red, green, blue) = color;

Ответ 5

Существуют ли какие-либо сценарии, где это может быть полезно?

Я испытал один в мире Java.

В случае перечислений вы знаете все возможные значения во время компиляции.

Тем не менее, есть время до компиляции, когда вы можете не знать все значения (пока) или в которых вы просто не намерены реализовать какие-либо значения.

В ходе разработки API я внедрил перечисление без каких-либо значений, чтобы включить ссылку на него с других интерфейсов. Я добавил значения позже.