为什么我可以将0.0分配给枚举值,而不是1.0

feO*_*O2x 87 c#

出于好奇:为什么我可以将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
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为数字类型和枚举值之间的转换只允许通过强制转换?那就是我可以编写Foo value2 = (Foo) 1.0;以便第2行Main可以编译.为什么0.0C#中的值有例外?

Eri*_*ert 97

乔恩的回答是正确的.我会补充以下几点.

  • 我造成了这个愚蠢而令人尴尬的错误.很多道歉.

  • 这个错误是由于我误解了编译器中"表达式为零"谓词的语义而引起的; 我相信它只检查整数零相等,实际上它正在检查更多的"这是这种类型的默认值吗?" 事实上,在早期版本的bug中,实际上可以将任何类型的默认值分配给枚举!它现在只是数字的默认值.(课程:仔细命名助手谓词.)

  • 我试图实现的行为,我搞砸了实际上是一个稍微不同的bug的解决方法.你可以在这里阅读整个可怕的故事: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(教训:在修复旧版时很容易引入新的更糟糕的错误那些.)

  • C#团队决定将这种错误行为归咎于而不是修复它,因为破坏现有代码而没有引人注目的好处的风险太高了.(课程:第一次就把它弄好!)

  • 我在Roslyn中编写的用于保存此行为的代码可以在方法HasImplicitEnumerationConversion中找到compilers\csharp\source\binder\semantics\conversions\conversions.cs- 请参阅它以获取有关Roslyn行为的更多详细信息.(请注意,我把命名谓词以及对心脏的教训- HasImplicitEnumerationConversion,IsNumericType以及IsConstantNumericZero所有不正是他们说在锡我在转换目录中写道:几乎所有的代码,我鼓励你阅读这一切因为有.关于C#如何与评论中的规范不同的许多有趣的事实.我使用SPEC VIOLATION进行装饰,以便于查找.)

还有一个兴趣点:C#还允许在枚举初始值设定项中使用任何枚举值,无论其是否为zeroness:

enum E { A = 1 }
enum F { B = E.A }  // ???
Run Code Online (Sandbox Code Playgroud)

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

  • 这真的很酷,我终于看到你写的代码了.Roslyn源代码是开源的,真棒.现在我完全理解存在有效的原因(技术/法律)不提供更改历史记录,但是看到更改历史记录以查看代码是如何演变的,这将是非常棒的. (10认同)
  • @Aidiakapi:的确,受影响的人数应该很少; 它不是零.C#团队非常重视变革.*你*很容易说最好做出修复; 你不必与愤怒的客户打交道,他们会打电话给你的副总裁抱怨你的微不足道的改变没有任何好处,无论如何都会延迟他们的系统集成一天. (5认同)
  • 它变得更糟.所有这些重大更改都将(理想情况下)列在Microsoft的Framework迁移指南中.此列表越长,用户迁移其应用程序的犹豫不决.因此,即使是轻微的突破性变化也会导致:1.少数应用程序要破解.2.少数用户拒绝升级(即使问题不影响他们).3.少数用户浪费资源来评估突破性变化是否会影响他们.4.来自#1,#2和#3的用户向其他人投诉. (3认同)

Jon*_*eet 94

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

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

隐式枚举转换允许将decimal-integer-literal 0转换为任何枚举类型和任何可以为其类型为枚举类型的可空类型.在后一种情况下,通过转换为基础枚举类型并包装结果来评估转换(第4.1.10节).

我以前曾与C#团队讨论过这个问题:他们希望将意外转换从0.0(实际上是0.0m和0.0f)移除到枚举值,但不幸的是我收集了它破坏了太多的代码 - 尽管它应该永远不会被允许.

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

const int Zero = 0;
...

SomeEnum x = Zero;
Run Code Online (Sandbox Code Playgroud)

尽管事实Zero是一个常量表达式但不是十进制整数字面值.

我不会惊讶地看到C#规范将来会改变以允许任何值为0的整数常量表达式(即模仿mcs),但我不希望浮点转换正式变得正确.(关于预测C#的未来,我当然错了,当然......)

  • "它破坏了太多的代码" - 很难想象有任何理由来编写这样的代码. (4认同)
  • 根据规范,它只是意味着*literal*0.所以它应该拒绝`1-1` - 一个常量`int`表达式,其值为'0`.但正如您所观察到的,编译器与此处的规范不符. (3认同)
  • @ObsidianPhoenix:嗯不,因为`Test.Foo`的值是1,而不是0 ......再次,这与你写的'Test v1 =(Test)0;` - 完全相同,而且这种行为适用于*任何*值,它不是枚举中的命名值. (2认同)
  • @JonSkeet是否会在罗斯林修复? (2认同)

Kon*_*lph 10

C#中的枚举按定义为整数值.为了保持一致性,C#不应该接受这些赋值中的任何一个,而是0.0静默地视为整数0.这可能是来自C的保留,其中文字0被特别处理并且基本上可以采用任何给定类型 - 整数,浮点数,空指针......你可以命名它.

  • 问题是**为什么**?如果你去`IL` - 它将整数值推入堆栈`IL_0001:ldc.i4.0` (3认同)
  • 我认为这是其中一种情况,如果你看一下C#*规范*,它是不合法的,但是如果你看一下MS生产的任何C#编译器,它就是这样做的. (2认同)