0的隐式转换为枚举类型

3
在C#中,十进制字面量0可以隐式转换为枚举类型(或基础类型为枚举的可为空类型)。
根据C#规范,当前版本在GitHub上:
隐式枚举转换允许将十进制整数字面量0转换为任何枚举类型和任何基础类型为枚举类型的可空值类型。在后一种情况下,转换通过转换为基础枚举类型并包装结果(可空值类型)进行评估。
根据ECMA-334,第11.2.4节隐式枚举转换:
隐式枚举转换允许将十进制整数字面量0(或0L等)转换为任何枚举类型和任何基础类型为枚举类型的可空值类型。在后一种情况下,转换通过转换为基础枚举类型并包装结果(§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 不是一个字面量(literal),而是一个编译时常量,其值为0。以下情况也是如此:

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

然而,这些都可以正常工作;在此示例中,e6e7e8e9的值均为E.X

为什么会这样?是否有一个(更新的)标准规范,指出编译时常量为0的枚举也可以隐式转换为任何枚举类型,或者这是编译器在不完全遵循规范的情况下进行的操作?


自2006年以来,情况可能已经发生了变化。它适用于哪个版本的C#,哪个版本不适用? - Sinatr
我认为这在所有情况下都是相同的。适用于.NET框架4.6.2和.NET core 2.1。 - Marius Bancila
3
@MariusBancila,在这里你需要非常谨慎地使用语言......这是一个 编译器 问题 - ".NET framework 4.6.2 和 .NET core 2.1" 是 运行时 - 实际上,它们甚至没有发言权。在这里起作用的是 编译器 版本。 - Marc Gravell
当然,你说得对。我已经尝试了从ISO-2到C# 7.3的所有东西,它们都是一样的。 - Marius Bancila
1个回答

4
正如您所指出的,0 | 0 | E.X被绑定为(0 | 0) | E.X
Eric指出编译器在处理0 | 0 | E.X时没有遵循规范:
在我们获取完整的语法树后,我们遍历语法树以确保所有类型都能够正常工作。不幸的是,初始类型绑定传递奇怪地进行算术优化。它检测到0 | something并将其积极地替换为just something ,因此就编译器而言,第六种情况与第二种情况相同,这是合法的。拥挤!
Eric在评论中指出:
但(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的任何表达式都会发生这种情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接