【问题标题】:Implicit conversion of 0 to enums0 到枚举的隐式转换
【发布时间】:2026-01-10 06:05:02
【问题描述】:

在 C# 中,十进制文字 0 可以隐式转换为枚举(或基础类型为枚举的可空值)。

C# spec,GitHub 上的当前版本

隐式枚举转换 允许将 decimal_integer_literal 0 转换为任何 enum_type 以及基础类型为 enum_type 的任何 nullable_type。在里面 后一种情况下,转换是通过转换为 底层 enum_type 并包装结果(可空类型)。

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 不是文字,而是值为0 的编译时常量。以下情况也是如此:

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

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

这是为什么呢?标准中是否有(更新的)规范说编译时常量为 0 也可以隐式转换为任何枚举,或者这是编译器在不完全遵循规范的情况下所做的事情?

【问题讨论】:

  • 自 2006 年以来,情况可能已经发生了变化。它在哪个 C# 版本中有效,在哪个版本中无效?
  • 我觉得都是一样的。适用于 .NET 框架 4.6.2 和 .NET 核心 2.1。
  • @MariusBancila 你需要对这里的语言非常谨慎......这是一个编译器的东西 - “.NET framework 4.6.2 和 .NET core 2.1”是 runtimes - 坦率地说,他们甚至没有投票权。 compiler 版本在这里很重要。
  • 当然,你是对的。从 ISO-2 到 C# 7.3,我都尝试过。它们都一样。

标签: c# enums type-conversion implicit-conversion


【解决方案1】:

如您所述,0 | 0 | E.X 被绑定为 (0 | 0) | E.X

Eric 指出编译器不遵循 0 | 0 | E.X 的规范:

在我们得到一个完整的解析树之后,我们会遍历解析树以确保所有类型都能正常工作。不幸的是,初始类型绑定传递的算术优化已经够奇怪了。它检测到 0|something 并积极地将其替换为 just something ,因此就编译器而言,第六种情况与第二种情况相同,这是合法的。啊!

cmets 中的 Eric 笔记:

但是 (7-7)|E.X 确实会产生错误

似乎 Roslyn 在折叠常量方面比原生编译器更聪明一些。很可能他们的目标是提高效率,而不是担心在极端情况下保留 bug-for-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 的任何表达式都会发生这种情况。

【讨论】: