C# – Enum generic type constraint

In C# 7.3, Microsoft added the ability to specify an Enum as a generic constraint, like this:

string SomeMethod<T>(int value) where T : Enum
Code language: C# (cs)

Whenever you have a generic method, it’s a good idea to use generic type constraints. Without constraints, you would have to implement type checking in the generic method and throw exceptions if an invalid type was used. With generic type constraints, you get compile-time errors instead. Compile-time errors are always better than run-time exceptions.

In this article, I’ll show how this was dealt with before the Enum generic type constraint feature was added, and an example of converting a generic method to use this new feature.

How it was done before

Before the Enum generic type constraint feature was added, your best option was to use the struct generic type constraint, and optionally do an enum type check using typeof(T).IsEnum, like this:

public static string GetName<T>(this int value) where T : struct { if (!typeof(T).IsEnum) throw new ArgumentException($"{typeof(T)} is not an enum"); return Enum.GetName(typeof(T), value); }
Code language: C# (cs)

The following code tries to use this method with a struct type (Int32):

400.GetName<int>();
Code language: C# (cs)

This results in a run-time exception, because it fails the enum type check.

This is why the addition of the Enum generic type constraint feature is important. If this method were using the Enum constraint, instead of getting the run-time exception, it would get this compile-time error:

Error CS0315 The type ‘int’ cannot be used as type parameter ‘T’ in the generic type or method ‘EnumExtensions.GetName(int)’. There is no boxing conversion from ‘int’ to ‘System.Enum’.

I mentioned the enum type check is optional. This is because the Enum helper methods, like Enum.GetName(), do an enum type check, which means it’s not always necessary to do your own check. They throw the following exception:

System.ArgumentException: Type provided must be an Enum.

Example – Converting a generic method to use the Enum generic type constraint

In a previous article, I wrote about how to convert a list of strings into a set of enums. In the first iteration of that article, I used the struct generic type constraint. The method looked like this:

public static class EnumExtensions { public static HashSet<T> ToSet<T>(this List<string> statusCodes) where T : struct { return new HashSet<T>(statusCodes.Where(s => !string.IsNullOrWhiteSpace(s) && Int32.TryParse(s, out int intValue) && Enum.IsDefined(typeof(T), intValue)) .Select(s => Enum.Parse<T>(s))); } }
Code language: C# (cs)

Let’s see how this can be converted to use an Enum generic type constraint instead of the struct constraint.

First, change where T : struct to where T : Enum:

public static HashSet<T> ToSet<T>(this List<string> statusCodes) where T : Enum
Code language: C# (cs)

This results in the following compile-time error:

Error CS0453 The type ‘T’ must be a non-nullable value type in order to use it as parameter ‘TEnum’ in the generic type or method ‘Enum.Parse(string)’

This is because this method is using Enum.Parse(), which uses the struct generic type constraint. It has the following signature:

public static TEnum Parse<TEnum>(string value) where TEnum : struct;
Code language: C# (cs)

There are two ways to fix this problem:

  • Option 1 – Use the non-generic version of Enum.Parse(), like this:
.Select(s => (T)Enum.Parse(typeof(T), s)));
Code language: C# (cs)
  • Option 2 – Wrap Enum.Parse() in a method and use the Enum constraint:
public static class EnumExtensions { public static HashSet<T> ToSet<T>(this List<string> statusCodes) where T : Enum { return new HashSet<T>(statusCodes.Where(s => !string.IsNullOrWhiteSpace(s) && Int32.TryParse(s, out int intValue) && Enum.IsDefined(typeof(T), intValue)) .Select(s => s.Parse<T>())); } public static T Parse<T>(this string enumStr) where T : Enum { return (T)Enum.Parse(typeof(T), enumStr); } }
Code language: C# (cs)

I prefer option 2, because it’s very likely I’d need to use this method somewhere else in the code.

Note: In .NET 5, Microsoft added overloads using the Enum generic type constraint for a few of the Enum helper methods – but not all of them (like Enum.Parse()). You could wrap them and use the Enum constraint like I’ve done with Enum.Parse().

2 thoughts on “C# – Enum generic type constraint”

  1. There’s a third possible option, both Enum AND Struct
    e.g.,
    public static string Name(this T enumType) where T: struct, Enum
    {
    return Enum.GetName(typeof(T), enumType);
    }

    Reply
    • Good catch.

      So if someone is using just the where T : Enum constraint, and they call this on the Enum base class, then you’ll get an exception. I personally haven’t used the Enum base class like this, so I haven’t run into this issue before.

      Example code:

      Enum a = HttpStatusCode.BadRequest;
      a.Name();

      This throws ArgumentException: Type provided must be an Enum.

      If you use the where T : struct, Enum, then it will be a compile-time error because the Enum base class is not a struct.

      Reply

Leave a Comment