Подтвердить что ты не робот

Почему операции между различными типами перечислений разрешены в другой декларации перечисления, но не в другом месте?

Компилятор С# позволяет выполнять операции между различными типами перечислений в другой декларации типа перечисления, например:

public enum VerticalAnchors
{
    Top=1,
    Mid=2,
    Bot=4
}

public enum HorizontalAnchors
{
    Lef=8,
    Mid=16,
    Rig=32
}

public enum VisualAnchors
{
    TopLef = VerticalAnchors.Top | HorizontalAnchors.Lef,
    TopMid = VerticalAnchors.Top | HorizontalAnchors.Mid,
    TopRig = VerticalAnchors.Top | HorizontalAnchors.Rig,
    MidLef = VerticalAnchors.Mid | HorizontalAnchors.Lef,
    MidMid = VerticalAnchors.Mid | HorizontalAnchors.Mid,
    MidRig = VerticalAnchors.Mid | HorizontalAnchors.Rig,
    BotLef = VerticalAnchors.Bot | HorizontalAnchors.Lef,
    BotMid = VerticalAnchors.Bot | HorizontalAnchors.Mid,
    BotRig = VerticalAnchors.Bot | HorizontalAnchors.Rig
}

но запрещает их внутри кода метода, то есть операцию:

VerticalAnchors.Top | HorizontalAnchors.Lef;

Помечено с этой ошибкой:

Оператор '|' не может применяться к операндам типа "VerticalAnchors" и "HorizontalAnchors".

Там обходной путь, конечно:

(int)VerticalAnchors.Top | (int)HorizontalAnchors.Lef

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

4b9b3361

Ответ 1

На самом деле это не в спецификации , насколько я могу судить. Есть что-то связанное:

Если объявление элемента перечисления имеет константное выражение инициализатор, значение этого постоянного выражения, неявнопреобразованный в базовый тип перечисления, является связанным значением члена перечисления.

Хотя VerticalAnchors.Top & HorizontalAnchors.Lef имеет тип VerticalAnchors, он может быть неявно преобразован в VisualAnchors. Но это не объясняет, почему само константное выражение поддерживает неявные преобразования везде.

На самом деле явно явно против spec:

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

Если я ничего не пропустил, спецификация не только не позволяет этого явно, но и запрещает. В соответствии с этим предположением это будет ошибка компилятора.

Ответ 2

Поскольку вы не задавали вопрос в своем вопросе, я притворяюсь, что вы задали несколько интересных вопросов и ответили им:

Верно ли, что внутри объявления перечисления вы можете использовать значения других перечислений в инициализаторах?

Да. Вы можете сказать

enum Fruit { Apple }
enum Animal { Giraffe = Fruit.Apple }

Несмотря на то, что было бы нецелесообразно назначать Fruit.Apple переменной типа Animal без трансляции.

Этот факт иногда удивляет. На самом деле я сам был удивлен. Когда я впервые попытался это сделать, чтобы проверить часть компилятора, я подумал, что это возможная ошибка.

Где в спецификации говорится, что это законно?

В разделе 14.3 говорится, что инициализатор должен быть константой и что константа будет преобразована в базовый тип перечисления. Члены Enum являются константами.

А, но как насчет этого случая?

enum Fruit { Apple = 1 }
enum Shape { Square = 2 }
enum Animal { Giraffe = Fruit.Apple | Shape.Square }

Это выражение, в первую очередь, не является юридическим постоянным выражением, так что?

Хорошо, ты меня туда. В разделе 14.3 также говорится, что перечисляемые элементы, используемые в инициализаторах, не нужно бросать, но неясно, означает ли это, что инициализируются члены инициализации перечисления или члены любого перечисления. Либо это разумная интерпретация, но без конкретного языка, легко думать, что первое значение является предполагаемым.

Это, таким образом, известный недостаток; Несколько лет назад я указал его Мадзу, и он никогда не был разрешен. С одной стороны, спецификация явно не допускает этого. С другой стороны, поведение полезно и в духе, если не полностью в письме, спецификации.

В основном, что делает реализация, когда он обрабатывает инициализатор enum, он рассматривает все члены перечисления как постоянные выражения их базового типа. (Конечно, это необходимо для обеспечения того, что определения перечисления являются ациклическими, но это, возможно, лучше оставить для другого вопроса.) Поэтому он не "видит", что Fruit и Shape не имеют определенного оператора или.

Хотя формулировка спецификации, к сожалению, не ясна, это желательная функция. На самом деле я часто использовал его в команде Roslyn:

[Flags] enum UnaryOperatorKind { 
  Integer = 0x0001, 
  ... 
  UnaryMinus = 0x0100,
  ... 
  IntegerMinus = Integer | UnaryMinus
  ... }

[Flags] enum BinaryOperatorKind { 
  ...
  IntegerAddition = UnaryOperatorKind.Integer | Addition
  ... }

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

Ответ 3

С# позволяет определение значений enum содержать выражение с постоянным значением, так что значение enum может быть комбинацией перечислений, например. [Flags]. Компилятор вычисляет каждое значение перечисления в выражении как int (обычно), поэтому вы можете выполнять побитовые и арифметические операции над значением перечисления.

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

Ответ 4

Интересно. Вы также можете спросить, почему это разрешено:

enum MyType
{
  Member = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase,
}

если это не разрешено:

var local = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase;

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

enum MyType
{
  Member = (int)(DayOfWeek.Thursday) | (int)(StringComparison.CurrentCultureIgnoreCase),
}

Я нахожу это очень странным. Я знал, что вы можете использовать значения одного и того же перечисления напрямую (не указывая приведение к базовому типу), как в последней строке:

enum SomeType
{
  Read = 1,
  Write = 2,
  ReadWrite = Read | Write,
}

но я нахожу очень удивительным, что другие члены перечисления также передаются в их основные целые типы.