Create Generic method constraining T to an Enum

Solution 1:

Since Enum Type implements IConvertible interface, a better implementation should be something like this:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

This will still permit passing of value types implementing IConvertible. The chances are rare though.

Solution 2:

This feature is finally supported in C# 7.3!

The following snippet (from the dotnet samples) demonstrates how:

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Be sure to set your language version in your C# project to version 7.3.


Original Answer below:

I'm late to the game, but I took it as a challenge to see how it could be done. It's not possible in C# (or VB.NET, but scroll down for F#), but is possible in MSIL. I wrote this little....thing

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty
    
    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE
        
      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T
        
        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL
        
      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }
  
  RETURNDEF:
    ldarg defaultValue
    stloc return_value
  
  RETURNVAL:
    ldloc return_value
    ret
  }
} 

Which generates a function that would look like this, if it were valid C#:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

Then with the following C# code:

using MyThing;
// stuff...
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
    Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
    Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
    Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
}

Unfortunately, this means having this part of your code written in MSIL instead of C#, with the only added benefit being that you're able to constrain this method by System.Enum. It's also kind of a bummer, because it gets compiled into a separate assembly. However, it doesn't mean you have to deploy it that way.

By removing the line .assembly MyThing{} and invoking ilasm as follows:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

you get a netmodule instead of an assembly.

Unfortunately, VS2010 (and earlier, obviously) does not support adding netmodule references, which means you'd have to leave it in 2 separate assemblies when you're debugging. The only way you can add them as part of your assembly would be to run csc.exe yourself using the /addmodule:{files} command line argument. It wouldn't be too painful in an MSBuild script. Of course, if you're brave or stupid, you can run csc yourself manually each time. And it certainly gets more complicated as multiple assemblies need access to it.

So, it CAN be done in .Net. Is it worth the extra effort? Um, well, I guess I'll let you decide on that one.


F# Solution as alternative

Extra Credit: It turns out that a generic restriction on enum is possible in at least one other .NET language besides MSIL: F#.

type MyThing =
    static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
        /// protect for null (only required in interop with C#)
        let str = if isNull str then String.Empty else str

        Enum.GetValues(typedefof<'T>)
        |> Seq.cast<_>
        |> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
        |> function Some x -> x | None -> defaultValue

This one is easier to maintain since it's a well-known language with full Visual Studio IDE support, but you still need a separate project in your solution for it. However, it naturally produces considerably different IL (the code is very different) and it relies on the FSharp.Core library, which, just like any other external library, needs to become part of your distribution.

Here's how you can use it (basically the same as the MSIL solution), and to show that it correctly fails on otherwise synonymous structs:

// works, result is inferred to have type StringComparison
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
// type restriction is recognized by C#, this fails at compile time
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);

Solution 3:

C# ≥ 7.3

Starting with C# 7.3 (available with Visual Studio 2017 ≥ v15.7), this code is now completely valid:

public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, Enum
{
 ...
}

C# ≤ 7.2

You can have a real compiler enforced enum constraint by abusing constraint inheritance. The following code specifies both a class and a struct constraints at the same time:

public abstract class EnumClassUtils<TClass>
where TClass : class
{

    public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, TClass
    {
        return (TEnum) Enum.Parse(typeof(TEnum), value);
    }

}

public class EnumUtils : EnumClassUtils<Enum>
{
}

Usage:

EnumUtils.Parse<SomeEnum>("value");

Note: this is specifically stated in the C# 5.0 language specification:

If type parameter S depends on type parameter T then: [...] It is valid for S to have the value type constraint and T to have the reference type constraint. Effectively this limits T to the types System.Object, System.ValueType, System.Enum, and any interface type.