21

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

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

4

4 回答 4

11

由于您没有在问题中提出问题,因此我将假装您提出了一些有趣的问题并回答:

在枚举声明中,您是否可以在初始化程序中使用其他枚举的值?

是的。你可以说

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

即使在没有强制转换的情况下分配Fruit.Apple给类型变量是不合法Animal的。

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

规范中的哪里说这是合法的?

14.3 节说初始化器必须是一个常量,并且该常量将被转换为枚举的底层类型。枚举成员是常量。

啊,但是这个案子呢?

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

该表达式首先不是合法的常量表达式,那是怎么回事?

好的,你让我到了那里。第 14.3 节也确实说初始化器中使用的 enum 成员不需要强制转换,但不清楚这是否意味着正在初始化的 enum 的成员或任何 enum的成员。要么是一个合理的解释,但没有具体的语言,很容易认为前者的意思是预期的意思。

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

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

尽管不幸的是规范措辞尚不清楚,但这是一个理想的功能。事实上,我在 Roslyn 团队中经常使用它:

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

[Flags] enum BinaryOperatorKind { 
  ...
  IntegerAddition = UnaryOperatorKind.Integer | Addition
  ... }

能够混合从各种枚举中提取的标志非常方便。

于 2013-01-26T23:25:45.727 回答
10

据我所知,它实际上不在规范中。有一些相关的:

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

虽然VerticalAnchors.Top & HorizontalAnchors.Lef有类型VerticalAnchors,但它可以隐式转换为VisualAnchors. 但这并不能解释为什么常量表达式本身在任何地方都支持隐式转换。

实际上,它似乎明确违反规范:

常量表达式的编译时求值使用与非常量表达式的运行时求值相同的规则,除了在运行时求值会引发异常的情况下,编译时求值会导致发生编译时错误。

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

于 2013-01-26T23:11:37.800 回答
2

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

在枚举定义之外,您必须先将枚举转换为原始类型,然后再对其执行操作。

于 2013-01-26T21:53:41.947 回答
1

有趣的。您还可以询问为什么允许这样做:

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,
}

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

于 2013-01-26T22:53:12.580 回答