为什么我可以给枚举值赋值0.0,而不能赋值1.0 ?

本文关键字:赋值 不能 我可以 枚举 为什么 | 更新日期: 2023-09-27 18:04:19

只是出于好奇:为什么我可以将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;,以便Main中的第2行可以编译。为什么在c#中有一个异常值0.0 ?

为什么我可以给枚举值赋值0.0,而不能赋值1.0 ?

Jon的答案正确。我想补充以下几点:

  • 我造成了这个愚蠢和尴尬的错误。许多道歉。

  • 这个bug是由于我误解了"表达式是零"的语义而导致的。编译器中的谓词;我以为它只检查整数0相等,而实际上它在检查更多的"这是该类型的默认值吗?"事实上,在该错误的早期版本中,实际上可以将任何类型的默认值赋值给enum!现在只有默认值的数字。(教训:仔细命名你的辅助谓词)

  • 我试图实现的行为,我搞砸了实际上是一个稍微不同的错误的解决方案。你可以在这里阅读整个可怕的故事:https://learn.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-one和https://learn.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-two(教训:在修复旧的bug时,很容易引入新的更严重的bug。)

  • c#团队决定保留这个有bug的行为,而不是修复它,因为没有引人注目的好处而破坏现有代码的风险太高了。(教训:第一次就做对!)

  • 我在Roslyn中编写的代码可以在https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs中的方法IsConstantNumericZero中找到,以了解Roslyn行为的更多细节。我在Conversions目录中编写了几乎所有的代码;我鼓励你阅读所有的内容,因为在注释中有许多关于c#如何偏离规范的有趣事实。我用SPEC VIOLATION来装饰它们,使它们更容易找到。

还有一点值得注意:c#还允许在枚举初始化器中使用任何枚举值,而不管其为零:
enum E { A = 1 }
enum F { B = E.A }  // ???

规范对于这是否应该合法有些模糊,但是,由于这在编译器中已经存在很长时间了,新的编译器可能会保持这种行为。

你可以使用0.0的一个bug。编译器隐式地将所有值为0的常量表达式视为0。

现在,它是正确的编译器允许从常量int表达式0隐式转换为您的枚举,根据c# 5规范的第6.1.3节:

隐式枚举转换允许将十进制整型字面值0转换为任何枚举类型和其基础类型为枚举类型的任何可空类型。在后一种情况下,通过转换为基础的enum类型并包装结果来计算转换(第4.1.10节)。

我曾经和c#团队讨论过这个问题:他们希望删除意外的从0.0(实际上是0.0m和0.0f)到enum值的转换,但不幸的是,我收集到它破坏了太多的代码——即使它一开始就不应该被允许。

Mono mcs编译器禁止所有这些浮点转换,尽管允许:

const int Zero = 0;
...
SomeEnum x = Zero;

,尽管Zero是一个常量表达式,但不是一个小数整型文字。

如果将来c#规范改变为允许任何值为0的整型常量表达式(即模仿mcs),我不会感到惊讶,但我不会期望浮点数转换正式是正确的。(当然,我之前预测c#的未来是错误的……)

c#中的枚举根据定义是整数值。为了一致性,c#不应该接受这两种赋值,但是0.0被静默地当作整型的0来处理。这可能是C语言的延续,在C语言中,文字0被特殊处理,基本上可以接受任何给定的类型——整数、浮点数、空指针……你能想到的。

enum实际上(在所有支持它的语言中)是一种处理有意义且唯一的字符串(标签)而不是数值的方法。因此,在您的示例中,当处理Foo枚举数据类型时,您应该只使用BarBaz。你永远不应该使用(比较或赋值)整数,即使许多编译器允许你这样做(枚举在内部通常是整数),在这种情况下,0.0被编译器粗心地当作0处理。

从概念上讲,将整数n添加到枚举值中应该是正确的,以便在行中得到n值,或者取val2-val1以查看它们之间的距离,但除非语言规范明确允许这样做,否则我将避免这样做。(可以把枚举值想象成C指针,就像您可以使用它的方式一样。)没有理由不能用浮点数和它们之间的固定增量来实现枚举,但我还没有听说过在任何语言中这样做。