64

Now that we have enum constraint, why doesn't compiler allow me to write this code?

public static TResult? ToEnum<TResult>(this String value, TResult? defaultValue)
    where TResult : Enum
{
    return String.IsNullOrEmpty(value) ? defaultValue : (TResult?)Enum.Parse(typeof(TResult), value);
}

The compiler says:

Error CS0453 The type 'TResult' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable'

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
Kirill Kovalenko
  • 2,121
  • 16
  • 18
  • `System.Enum` inherits from `System.ValueType` so I expect it to be a value not a class. – Kirill Kovalenko May 15 '18 at 13:37
  • @mjwills it's worth noting that a class cannot be Nullable because it's a reference type (which can be null). Nullable is reserved for value types which are distinctly different to the ValueType class that Enum inherits from. – Danielle Summers May 15 '18 at 13:40
  • 5
    @DanielleSummers At least until C#8 where we get nullable reference types... – DavidG May 15 '18 at 13:41
  • This seems like a serious flaw in the implementation of this new generic constraint. I would expect it to not refer to the `System.Enum` class specifically, but rather just types defined with the `enum` keyword, which are always value types. I would post this as a bug on the C# github, if it's not already there. – JamesFaix May 15 '18 at 13:41
  • 2
    @DavidG - meanwhile I'm still waiting for non-nullable reference types... – Corak May 15 '18 at 13:42
  • @Corak That's the same thing really – DavidG May 15 '18 at 13:42
  • 2
    @JamesFaix It's not really supposed to be a new generic constraint. It's the same constraint that has always been valid in CIL but has been artificially restricted in C# until 7.3. Because of that, I don't expect it to be useful to report it as a bug. –  May 15 '18 at 13:43
  • `I would expect it to not refer to the System.Enum class specifically, but rather just types defined with the enum keyword` Why would you expect that? When other types are mentioned in constraints does it only allow types that inherit from it? Or also the type itself? – mjwills May 15 '18 at 13:45
  • 1
    @KirillKovalenko: `System.Enum` is a [special class](https://stackoverflow.com/questions/29961823/what-exactly-is-a-special-class)(which can't be used as type parameter constraint anyway). It is so special that it inherits from `System.ValueType` but is a reference type. https://stackoverflow.com/questions/14561338/enum-is-reference-type-or-value-type – Tim Schmelter May 15 '18 at 13:45
  • 4
    @mjwills Because the `Enum` base class is a weird case in the .NET type hierarchy, and all classes that inherit from it are value types. I thought the new constraint was akin to the `class` or `struct` constraints, not just allowing a base class constraint that was forbidden before. – JamesFaix May 15 '18 at 13:46
  • I started with the same (incorrect) assumption and ended up using `struct, Enum`. See my question [Why is a generic type constrained by 'Enum' failing to qualify as a 'struct'?](https://stackoverflow.com/questions/50294032/why-is-a-generic-type-constrained-by-enum-failing-to-qualify-as-a-struct) – Stephen Kennedy May 15 '18 at 15:26
  • What an absolutely useless feature. You can constrain the type to `Enum`, but you can't do anything with it! You can't cast it to 'int', you can't combine flags with `|`. These all give errors: `TEnum val; var i = (int)val; //ERROR: "Cannot convert type 'TEnum' to 'int'"` or `TEnum a, b; var c = a | b; //ERROR: "Operator '|' cannot be applied to type 'TEnum' and 'TEnum'."` Useless. – Triynko May 15 '18 at 22:54
  • @Triynko That is irrelevant for this question - please ask your own question if you are interested in discussing that specific issue. – mjwills May 15 '18 at 23:10

2 Answers2

101

You can, but you have to add another constraint: the struct constraint.

public static void DoSomething<T>(T? defaultValue) where T : struct, Enum
{
}
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • 14
    That isn't a workaround. It is just what the specs tell us to do @KirillKovalenko – Patrick Hofman May 15 '18 at 13:58
  • @PatrickHofman Can you link to the specs? I must have missed that part in my reading. – thermite Dec 14 '18 at 19:43
  • 1
    Because `Nullable` requires `T` to be a `struct` type @thermite. – Patrick Hofman Dec 15 '18 at 12:54
  • Thanks @patrick, I found it in the docs for nullable. I was looking in the docs at enums and generics... – thermite Dec 17 '18 at 05:30
  • @thermite can you link to the doc you found? – cpcolella Oct 11 '19 at 17:22
  • 1
    The docs for `Nullable` show the `where T : struct` constraint. https://learn.microsoft.com/en-us/dotnet/api/system.nullable-1?view=netcore-3.1 . Here is the constraint in the source code: https://github.com/microsoft/referencesource/blob/master/mscorlib/system/nullable.cs#L27 / https://github.com/dotnet/runtime/blob/v5.0.0-preview.5.20278.1/src/libraries/System.Runtime/ref/System.Runtime.cs#L2864 – scottt732 Jun 17 '20 at 18:24
20

Because System.Enum is a class, you cannot declare a variable of type Nullable<Enum> (since Nullable<T> is only possible if T is a struct).

Thus:

Enum? bob = null;

won't compile, and neither will your code.

This is definitely strange (since Enum itself is a class, but a specific Enum that you define in your code is a struct) if you haven't run into it before, but it is clearly a class (not a struct) as per the docs and the source code.

mjwills
  • 23,389
  • 6
  • 40
  • 63
  • See https://stackoverflow.com/questions/15374657/if-system-enum-is-a-class-how-can-it-also-be-a-valuetype @Freggar. – mjwills May 15 '18 at 13:51