5

While answering another question, Jon Skeet mentioned that there is a weird thing going on with the definition of enums. His answer.

He states that redifining the underlying type of an enum is only possible with the type-aliases and not with the framework types (int is valid, Int32 not, etc.)

public enum Foo : UInt32 {} // Invalid
public enum Bar : uint   {} // Valid

Now I tried to reproduce that (with C#6/Roslyn in VS2015), and I didn't come to the same conclusion:

public enum TestEnum : UInt32
{

}

and

public enum MyEnum : uint
{

}

are both totally valid. Why is that so? Or has what changed?


EDIT:

So to clear up things, it was a change in C#6, that has not been documented yet, and it will be documented soon, as you can read from this git issue on the Roslyn Github

Community
  • 1
  • 1
Mafii
  • 7,227
  • 1
  • 35
  • 55

3 Answers3

7

It’s certainly odd that this is working now with C# 6. The current in-progress specification still lists the following grammar for specifying a base in enum declarations:

enum_base
    : ':' integral_type
    ;

And the integral types are defined as actual fixed tokens:

integral_type
    : 'sbyte'
    | 'byte'
    | 'short'
    | 'ushort'
    | 'int'
    | 'uint'
    | 'long'
    | 'ulong'
    | 'char'
    ;

Judging by this language specification, using some other base type that does not appear in that list of static type identifiers should be rejected by the parser and cause a syntax error.

Given that that’s not what happens, there are two possibilities: Either the parser was changed deliberately to accept the non-aliased types there, or the parser incorrectly accepts those.

If we look at the implementation of Roslyn, then we can see why this requirement in the specification is not enforced. The enum declaration parser simply does not check for it but parses any type:

BaseListSyntax baseList = null;
if (this.CurrentToken.Kind == SyntaxKind.ColonToken)
{
    var colon = this.EatToken(SyntaxKind.ColonToken);
    var type = this.ParseType(false);
    var tmpList = _pool.AllocateSeparated<BaseTypeSyntax>();
    tmpList.Add(_syntaxFactory.SimpleBaseType(type));
    baseList = _syntaxFactory.BaseList(colon, tmpList);
    _pool.Free(tmpList);
}

At this point, it does not really differ much from the code for “normal” inheritance. So any type restriction does not apply on the syntax level, but on the semantic level—at which point type alias are probably already evaluated.

So this seems to be a bug: Either in the specification, or in the parser. Given that the specification is still a work in progress, this might be fixed later.

poke
  • 369,085
  • 72
  • 557
  • 602
2

Either it's an oversight in the new compiler or they made a decision not to have that inconsistency (?) in the language anymore.

At the moment there doesn't seem to be an official specification, only a not-so-official-looking git repository where work is being done:

https://github.com/ljw1004/csharpspec/blob/gh-pages/enums.md

At the moment it looks like not using the aliases for the underlying enum type should be an error. The language looks to be the same as the previous official spec. But since both the compiler and spec seem to still be works in progress, it's hard to say which one is correct.

Matti Virkkunen
  • 63,558
  • 9
  • 127
  • 159
  • But wouldn't the underlying type have to be expanded to the framework type? that seems to fit the definition. – Daniel A. White Jun 02 '16 at 10:30
  • 1
    @DanielA.White: If you read the question linked by OP it mentions that the spec specifies that the underlying type must match the `integral_type` production which is defined only as a list of the C# built-in keywords, and nothing else. That part is the same in the work in progress spec. – Matti Virkkunen Jun 02 '16 at 10:31
  • just looking at the definition of `integral_type` it sounds like anywhere it is used it must represent the framework type. – Daniel A. White Jun 02 '16 at 10:35
  • 1
    The `integral_type` definition itself is only a list of words (`'sbyte' | 'byte' | 'short' | ...`), so it can only refer to the magic C#-specific keywords. The way I understand is that in most places when talking about types it refers to a production higher in the hiearchy, which permits references to arbitrary classes or structs, and therefore `System.Int32` etc. The enum bit is the only thing that refers directly to the `integral_type` production. – Matti Virkkunen Jun 02 '16 at 10:41
1

This was an intentional language change in C# 6, whose specs have only been published in draft form. It was such a small change that we keep forgetting to put it into the specs.

See also https://github.com/dotnet/roslyn/issues/623

Neal Gafter
  • 3,293
  • 20
  • 16