在C#中,可以将十进制文字0
隐式转换为枚举(或其基础类型为枚举的可空值)。
C# spec,GitHub上的当前版本
隐式枚举转换 允许将decimal_integer_literal 0转换为任何enum_type 以及其基础类型为enum_type的任何nullable_type。在里面 在后一种情况下,通过转换为 基本的enum_type并包装结果(可为null的类型)。
ECMA-334,第11.2.4节隐式枚举转换
隐式枚举转换允许使用十进制整数 0(或0L等)将转换为任何枚举类型和任何 nullable-value-type,其基础类型是枚举类型。在里面 在后一种情况下,通过转换为 基础枚举类型并包装结果(第9.3.11节)
基于此,以下所有示例均应合法。此示例来自Eric Lippert的文章The Root Of All Evil, Part One。
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;
但是,正如Eric所解释的,以下情况应该是非法的:
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也可以隐式转换为任何枚举,或者这是编译器在没有完全遵循规范的情况下执行的操作?
答案 0 :(得分:3)
您已经注意到,0 | 0 | E.X
被绑定为(0 | 0) | E.X
。
Eric指出编译器未遵循0 | 0 | E.X
的规范:
在获得完整的解析树之后,我们遍历解析树,以确保所有类型都能正常工作。不幸的是,初始类型绑定通过非常奇怪地进行了算法优化。它检测到0 | something并积极地将其替换为0 | something,就编译器而言,第六种情况与第二种情况相同,这是合法的。啊!
评论中的埃里克笔记:
但是(7-7)| E.X确实会产生错误
似乎Roslyn在折叠常量方面比本机编译器要聪明一些。他们很可能在这里着眼于效率,而不必担心在极端情况下保留逐个bug的行为。
出于相同的原因,现在似乎完全相同的问题似乎适用于7 - 7
或编译器在初始类型绑定过程中可以评估为0
的任何其他表达式。
我认为不断发生折叠here:
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
的任何表达式都会发生这种情况。