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

21 .net c# 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
}
Run Code Online (Sandbox Code Playgroud)

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

VerticalAnchors.Top | HorizontalAnchors.Lef;
Run Code Online (Sandbox Code Playgroud)

被标记为此错误:

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

当然有一种解决方法:

(int)VerticalAnchors.Top | (int)HorizontalAnchors.Lef
Run Code Online (Sandbox Code Playgroud)

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

Eri*_*ert 11

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

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

是.你可以说

enum Fruit { Apple }
enum Animal { Giraffe = Fruit.Apple }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

enum Fruit { Apple = 1 }
enum Shape { Square = 2 }
enum Animal { Giraffe = Fruit.Apple | Shape.Square }
Run Code Online (Sandbox Code Playgroud)

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

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

因此这是一个已知的缺陷; 几年前我向Mads指出了它,它从未得到解决.一方面,规范并没有明确允许它.另一方面,该行为既有用又在本质上,即使不完全在说明书的字母中.

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

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

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

[Flags] enum BinaryOperatorKind { 
  ...
  IntegerAddition = UnaryOperatorKind.Integer | Addition
  ... }
Run Code Online (Sandbox Code Playgroud)

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

  • @EricLippert:感谢您对我不存在的问题的精彩回答(再次抱歉).但这种行为对我来说似乎很奇怪,大多数我都很想知道它是如何形成的.您的答案一直是了解真正发生的事情的指南.但我认为说这是一个"错误"更准确.但是,我希望它永远不会被修复,因为我同意你的看法,它非常方便. (9认同)
  • 你甚至可以做一些疯狂的事情,比如`enum Animal {Giraffe = Fruit.Apple*Shape.Square << DayOfWeek.Thursday,}`.通常情况下,你永远不能用枚举类型的操作数进行乘法或位移,但在这里你可以! (2认同)

usr*_*usr 10

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

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

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

实际上,它显然违反了规范:

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

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