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

时间:2013-01-26 21:18:02

标签: c# .net enums

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 个答案:

答案 0 :(得分: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节也说初始化器中使用的枚举成员不需要被强制转换,但是不清楚它是否意味着枚举的成员任何枚举的成员。要么是合理的解释,要么没有特定的语言,很容易认为前者的含义是预期的。

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

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

虽然遗憾的是,规范措辞不明确,但这是一个理想的功能。事实上,我经常在Roslyn团队中使用它:

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

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

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

答案 1 :(得分:10)

据我所知,它实际上不在spec。有一些相关的东西:

  

如果枚举成员的声明具有常量表达式   初始化程序,该常量表达式的值,隐式   转换为枚举的基础类型,是关联的值   枚举成员。

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

实际上,它明显是反对规范:

  

常量表达式的编译时评估使用相同的   规则作为非常量表达式的运行时评估,除了   运行时评估会抛出异常,编译时   评估会导致发生编译时错误。

如果我没有遗漏某些内容,那么规范不仅不能明确地允许这一点,它也不允许这样做。在这种假设下,这将是一个编译器错误。

答案 2 :(得分:2)

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

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

答案 3 :(得分: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,
}

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