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

前端 未结 4 548
太阳男子
太阳男子 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.

    0 讨论(0)
  • 2021-02-12 12:47

    Interesting. You can also ask why this is allowed:

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

    when this is not allowed:

    var local = DayOfWeek.Thursday | StringComparison.CurrentCultureIgnoreCase;
    

    The reason seems to be that inside the declaration of an enum, in the enum member initializers, any enum value, even a value of an unrelated enum, is considered to be cast to its underlying type. So the compiler sees the above example as:

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

    I find this very strange. I knew that you could use values of the same enum directly (without stating the cast to the underlying type), as in the last line of:

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

    but I find it very surprising that other enums' members are also cast to their underlying integer types.

    0 讨论(0)
  • 2021-02-12 12:53

    It is actually not in the spec as far as I can tell. There is something related:

    If the declaration of the enum member has a constant-expression initializer, the value of that constant expression, implicitly converted to the underlying type of the enum, is the associated value of the enum member.

    Although VerticalAnchors.Top & HorizontalAnchors.Lef has type VerticalAnchors it can be implicitly converted to VisualAnchors. But this does not explain why the constant expression itself supports implicit conversions everywhere.

    Actually, it appears explicitly to be against the spec:

    The compile-time evaluation of constant expressions uses the same rules as run-time evaluation of non-constant expressions, except that where run-time evaluation would have thrown an exception, compile-time evaluation causes a compile-time error to occur.

    If I didn't miss something, the spec not only does not allows this explicitly, it disallows it. Under that assumption it would be a compiler bug.

    0 讨论(0)
  • 2021-02-12 13:00

    C# allows definition of enum values to contain constant value expression, so that an enum value can be a combination of enumerations, e.g. [Flags]. The compiler evaulates each enum value in the expression as int (usually), thus you can do bitwise and arithmetic operations on the enum value.

    Outside of an enum definition, you must cast the enum to a primitive type before performing an operation on it.

    0 讨论(0)
提交回复
热议问题