Why are operations between different enum types allowed in another enum declaration but not elsewhere?

前端 未结 4 546
太阳男子
太阳男子 2021-02-12 12:26

The C# compiler allows operations between different enum types in another enum type declaration, like this:

public enum VerticalAnchors
{
    Top=1,
    Mid=2,
          


        
4条回答
  •  遥遥无期
    2021-02-12 12:40

    Since you did not ask a question in your question, I'll pretend that you asked some interesting questions and answer them:

    Is it true that inside an enum declaration, you can use values of other enums in the initializers?

    Yes. You can say

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

    Even though it would not be legal to assign Fruit.Apple to a variable of type Animal without a cast.

    This fact is occasionally surprising. In fact, I myself was surprised. When I first tried doing that, to test part of the compiler, I thought it was a possible bug.

    Where in the spec does it say that is legal?

    Section 14.3 says that the initializer must be a constant, and that the constant will be converted to the underlying type of the enum. Enum members are constants.

    Ah, but what about this case?

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

    That expression isn't a legal constant expression in the first place, so what's up?

    OK, you got me there. Section 14.3 does also say that enum members used in initializers do not need to be cast, but it is unclear as to whether it means members of the enum being initialized or members of any enum. Either is a reasonable interpretation, but without specific language, it is easy to think that the former meaning is the intended one.

    This is thus a known flaw; I pointed it out to Mads a few years ago and it has never been resolved. On the one hand, the spec does not clearly allow it. On the other hand, the behaviour is both useful and in the spirit, if not entirely in the letter, of the specification.

    Basically, what the implementation does is when it is processing an enum initializer, it treats all enum members as being constant expressions of their underlying type. (Of course, it does need to ensure that enum definitions are acyclic, but that is perhaps better left for another question.) It therefore does not "see" that Fruit and Shape do not have an "or" operator defined.

    Though the spec wording is unfortunately not clear, this is a desirable feature. In fact, I used it frequently on the Roslyn team:

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

    It is very handy to be able to mix-n-match flags taken from various enums.

提交回复
热议问题