Почему я могу присвоить 0.0 значениям перечисления, но не 1.0

Просто из любопытства: почему я могу присвоить 0.0 переменной, которая имеет тип перечисления, но не 1.0? Посмотрите на следующий код:

public enum Foo
{
    Bar,
    Baz
}

class Program
{
    static void Main()
    {
        Foo value1 = 0.0;
        Foo value2 = 1.0;   // This line does not compile
        Foo value3 = 4.2;   // This line does not compile
    }
}

Я думал, что конверсии между численными типами и значениями перечисления разрешены только с помощью бросков? То есть я мог бы написать Foo value2 = (Foo) 1.0;, так что строка 2 в Main может компилироваться. Почему существует исключение для значения 0.0 в С#?

Ответ 1

Это ошибка, которую вы можете использовать 0.0. Компилятор неявно обрабатывает все константные выражения с нулевым значением как 0.

Теперь правильно для компилятора разрешить неявное преобразование из константного выражения int 0 в ваш перечисление в соответствии с разделом 6.1.3 спецификации С# 5:

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

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

Компилятор Mono mcs запрещает все эти преобразования с плавающей запятой, хотя это позволяет:

const int Zero = 0;
...

SomeEnum x = Zero;

несмотря на то, что Zero является константным выражением, но не десятичным целым-литералом.

Я бы не удивился, увидев изменение спецификации С# в будущем, чтобы разрешить любое целочисленное постоянное выражение со значением 0 (т.е. имитировать mcs), но я бы не ожидал, что преобразования с плавающей запятой никогда не будут официально будет правильным. (Я ошибался раньше, чтобы предсказать будущее С#, конечно...)

Ответ 2

Ответ Джона правильный. Я бы добавил к этому следующие моменты.

  • Я вызвал эту глупую и смущающую ошибку. Много извинений.

  • Ошибка была вызвана неправильным пониманием семантики предиката "expression is zero" в компиляторе; Я полагал, что он проверял только на целое равенство нулю, когда на самом деле он проверял больше по строкам "это значение по умолчанию этого типа?" Фактически, в более ранней версии ошибки было действительно возможно присвоить значение по умолчанию любого типа перечислению! Теперь это значения по умолчанию только по умолчанию. (Урок: Тщательно укажите предикаты вашего помощника.)

  • Поведение, которое я пытался реализовать, я испортил, было на самом деле обходным путем для немного другой ошибки. Здесь вы можете прочитать всю страшную историю: http://blogs.msdn.com/b/ericlippert/archive/2006/03/28/the-root-of-all-evil-part-one.aspx и здесь http://blogs.msdn.com/b/ericlippert/archive/2006/03/29/the-root-of-all-evil-part-two.aspx (Урок: это очень легко ввести новые худшие ошибки при установке старых.)

  • Команда С# решила закрепить это ошибочное поведение, а не зафиксировать его, потому что риск нарушения существующего кода без каких-либо убедительных преимуществ был слишком высоким. (Урок: сделайте это правильно в первый раз!)

  • Код, который я написал в Roslyn, чтобы сохранить это поведение, можно найти в методе HasImplicitEnumerationConversion в compilers\csharp\source\binder\semantics\conversions\conversions.cs - см. более подробную информацию о том, что такое поведение Рослина. (Обратите внимание, что я взял урок наименования ваших предикатов - HasImplicitEnumerationConversion, IsNumericType и IsConstantNumericZero все делают именно то, что они говорят на жестяне. Я написал почти весь код в каталоге Conversions, я рекомендую вам прочитать его все, так как есть много интересных фактов о том, как С# отклоняется от спецификации в комментариях Я украсил каждую из них SPEC VIOLATION, чтобы облегчить их поиск.)

Еще одна интересная тема: С# также позволяет использовать любое значение enum в инициализаторе перечисления независимо от его нулевости:

enum E { A = 1 }
enum F { B = E.A }  // ???

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

Ответ 3

Перечисления в С# по определению являются интегральными значениями. Для согласованности С# не должен принимать одно из этих назначений, но 0.0 молча воспринимается как интегральный 0. Вероятно, это ограничение с C, где литерал 0 обрабатывался специально и мог по существу принимать любой заданный тип - целое число, число с плавающей запятой, нулевой указатель... вы называете его.

Ответ 4

enum действительно предназначен (на всех языках, которые его поддерживают), чтобы быть способ работать со значимыми и уникальными строками (метками), а не с числовыми значениями. Таким образом, в вашем примере вы должны использовать Бар и Baz при работе с перечисляемым типом данных Foo. Вы никогда не должны использовать (сравнивать или назначать) целое число, даже если многие компиляторы позволят вам уйти с ним (перечисления обычно являются целыми внутренними), и в этом случае 0.0 беззаботно рассматривается как 0 компилятором.

Понятно, что было бы правильно добавить целочисленное число n в числовое значение, чтобы получить n значений дальше по строке или взять val2-val1, чтобы увидеть, насколько они далеко друг от друга, но если спецификация языка явно не позволяет это, я бы избежать этого. (Подумайте о том, что перечисленное значение похоже на указатель C, в том, как вы можете его использовать.) Нет смысла перечислять причины с числами с плавающей запятой и фиксированным приращением между ними, но я не слышал о это делается на любом языке.