为什么不同的枚举类型之间的操作允许在另一个枚举声明中而在其他地方不允许?

C#编译器允许在另一个枚举类型声明中的不同枚举类型之间进行操作,如下所示:

public enum VerticalAnchors { Top=1, Mid=2, Bot=4 } public enum HorizontalAnchors { Lef=8, Mid=16, Rig=32 } public enum VisualAnchors { TopLef = VerticalAnchors.Top | HorizontalAnchors.Lef, TopMid = VerticalAnchors.Top | HorizontalAnchors.Mid, TopRig = VerticalAnchors.Top | HorizontalAnchors.Rig, MidLef = VerticalAnchors.Mid | HorizontalAnchors.Lef, MidMid = VerticalAnchors.Mid | HorizontalAnchors.Mid, MidRig = VerticalAnchors.Mid | HorizontalAnchors.Rig, BotLef = VerticalAnchors.Bot | HorizontalAnchors.Lef, BotMid = VerticalAnchors.Bot | HorizontalAnchors.Mid, BotRig = VerticalAnchors.Bot | HorizontalAnchors.Rig } 

但禁止他们在方法代码中,即操作:

 VerticalAnchors.Top | HorizontalAnchors.Lef; 

被标记为此错误:

运营商’|’ 不能应用于’VerticalAnchors’和’Horizo​​ntalAnchors’类型的操作数。

当然有一种解决方法:

 (int)VerticalAnchors.Top | (int)HorizontalAnchors.Lef 

我很好奇这个编译器的行为。 为什么不同的枚举类型之间的操作允许在另一个枚举声明中而在其他地方不允许?

据我所知,它实际上不符合规范 。 有一些相关的东西:

如果枚举成员的声明具有常量表达式初始值设定项,则该常量表达式的值( 隐式转换为枚举的基础类型)是枚举成员的关联值。

虽然VerticalAnchors.Top & HorizontalAnchors.Lef具有类型VerticalAnchors但它可以隐式转换为VisualAnchors 。 但这并不能解释为什么常量表达式本身支持各处的隐式转换。

实际上,它明显违反规范:

常量表达式的编译时评估使用与非常量表达式的运行时评估相同的规则 ,但运行时评估会抛出exception的情况除外,编译时评估会导致编译时错误发生。

如果我没有错过任何东西,那么规范不仅没有明确地允许这一点,它也不允许它。 在这种假设下,这将是一个编译器错误。

既然你没有在问题中提出问题,我会假装你提出一些有趣的问题并回答它们:

在枚举声明中,您可以在初始值设定项中使用其他枚举的值吗?

是。 你可以说

 enum Fruit { Apple } enum Animal { Giraffe = Fruit.Apple } 

即使将Fruit.Apple分配给没有Fruit.Apple转换的Animal类型的变量也是不合法的。

这个事实偶尔会令人惊讶。 事实上,我自己也很惊讶。 当我第一次尝试这样做时,为了测试部分编译器,我认为这是一个可能的错误。

在规范中哪里说它是合法的?

第14.3节说初始化器必须是常量,并且常量将转换为枚举的基础类型。 枚举成员是常量。

啊,但是这个案子怎么样?

 enum Fruit { Apple = 1 } enum Shape { Square = 2 } enum Animal { Giraffe = Fruit.Apple | Shape.Square } 

这个表达式首先不是一个合法的常量表达式,所以它是什么?

好的,你让我在那里。 第14.3节也说明初始化器中使用的枚举成员不需要进行转换,但是不清楚它是否意味着枚举的成员被初始化或是任何枚举的成员。 要么是合理的解释,要么没有特定的语言,很容易认为前者的含义是预期的。

因此这是一个已知的缺陷; 几年前我向Mads指出了它,它从未得到解决。 一方面,规范并没有明确允许它。 另一方面,该行为既有用又符合规范中的精神,如果不完全在字母中。

基本上,实现的作用是在处理枚举初始化程序时,它将所有枚举成员视为其基础类型的常量表达式。 (当然,它确实需要确保枚举定义是非循环的,但这可能最好留给另一个问题。)因此,它并没有“看到” FruitShape没有定义“or”运算符。

虽然遗憾的是,规范措辞并不清楚,但这是一个理想的特征。 事实上,我经常在Roslyn团队中使用它:

 [Flags] enum UnaryOperatorKind { Integer = 0x0001, ... UnaryMinus = 0x0100, ... IntegerMinus = Integer | UnaryMinus ... } [Flags] enum BinaryOperatorKind { ... IntegerAddition = UnaryOperatorKind.Integer | Addition ... } 

能够从各种枚举中混合使用n-match标志非常方便。

C#允许枚举值的定义包含常量值表达式,因此枚举值可以是枚举的组合,例如[Flags] 。 编译器将表达式中的每个枚举值作为int (通常)进行检验,因此您可以对枚举值执行按位和算术运算。

在枚举定义之外,必须在对枚举执行操作之前将枚举转换为基本类型。

有趣。 你也可以问为什么这是允许的:

 enum MyType { Member = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase, } 

如果不允许这样做:

 var local = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase; 

原因似乎是在枚举的声明中,在枚举成员初始化器中,任何枚举值,甚至是无关枚举的值,都被认为是强制转换为其基础类型。 所以编译器将上面的例子视为:

 enum MyType { Member = (int)(DayOfWeek.Thursday) | (int)(StringComparison.CurrentCultureIgnoreCase), } 

我觉得这很奇怪。 我知道您可以直接使用相同枚举的值(不会将强制转换为基础类型),如下一行:

 enum SomeType { Read = 1, Write = 2, ReadWrite = Read | Write, } 

但我发现非常令人惊讶的是, 其他枚举的成员也被强制转换为它们的基础整数类型。