Неявное преобразование 0 в перечисления - PullRequest
3 голосов
/ 23 мая 2019

В C # десятичный литерал 0 может быть неявно преобразован в перечисление (или обнуляемый, базовый тип которого является перечислением).

C # spec , текущая версия на GitHub

Неявное преобразование перечисления позволяет преобразовать decimal_integer_literal 0 в любой enum_type и в любой nullable_type, базовый тип которого равентип enum_type.В последнем случае преобразование оценивается путем преобразования в базовый тип enum_type и переноса результата (типы Nullable).

ECMA-334 , раздел 11.2.4 Неявные преобразования перечисления

Неявное преобразование перечисления позволяет преобразовать десятичное целочисленное литеральное значение 0 (или 0L и т. Д.) В любой тип перечисления и в любой тип значения с нулевым значением, базовый тип которого является перечислениемтип.В последнем случае преобразование оценивается путем преобразования в базовый тип enum и переноса результата (§9.3.11)

Исходя из этого, все следующие примеры должны быть законными.Этот пример взят из статьи Эрика Липперта Корень зла, часть первая .

enum E
{
  X, Y, Z
}

E e1 = E.X;
E e2 = 0 | E.X;
E e3 = E.X | 0;
E e4 = E.X | 0 | 0;
E e5 = 0 | E.X | 0;

Однако, как объясняет Эрик, следующий случай должен быть незаконным:

E e6 = 0 | 0 | E.X;

Причина в том, что 0 | 0 | E.X - это то же самое, что и (0 | 0) | E.X, а 0 | 0 - не литерал, а константа времени компиляции со значением 0. То же самое верно для следующих случаев:

E e7 = 1 - 1;
E e8 = 2 - 1 - 1 + 0;
E e9 = (0L & 1);

Однако все это работает нормально;e6, e7, e8 и e9 в этом примере имеют значение E.X.

Почему это так?Есть ли в стандарте (более новая) спецификация, в которой говорится, что константы времени компиляции, которые равны 0, также могут быть неявно преобразованы в любое перечисление, или это то, что компилятор делает без точного следования спецификациям?

1 Ответ

3 голосов
/ 23 мая 2019

Как вы уже заметили, 0 | 0 | E.X связывается как (0 | 0) | E.X.

Эрик отметил, что компилятор не следует спецификации для 0 | 0 | E.X:

После того, как у нас есть полное дерево разбора, мы проходим через дерево разбора, чтобы убедиться, что все типы работают. К сожалению, начальная передача типа довольно странно выполняет арифметическую оптимизацию. Он обнаруживает 0 | что-то и агрессивно заменяет его просто чем-то, так что для компилятора шестой случай такой же, как и второй, что допустимо. Argh!

Эрик отмечает в комментариях:

но (7-7) | E.X правильно выдает ошибку

Кажется, что Roslyn немного умнее насчет констант сворачивания, чем нативный компилятор. Вероятно, они стремились к эффективности здесь, не заботясь о сохранении поведения «ошибка за ошибкой» в крайнем случае.

Похоже, теперь та же самая проблема относится к 7 - 7 или любому другому выражению, которое компилятор может вычислить до 0 во время этого начального прохода привязки типа по той же причине.

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

newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight);
if (newValue != null)
{
    return ConstantValue.Create(newValue, resultType);
}

Как видите, создается новый ConstantValue. Так что (0 | 0) | E.X складывается в 0 | E.X, где это первое 0 является константой Когда компилятор сворачивает 0 | E.X, он не осознает, что 0 не является литералом 0 в исходном источнике, а является константой, сгенерированной компилятором, и поэтому сворачивает ее, как если бы вы написали 0 | E.X первоначально.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...