Create Generic method constraining T to an Enum
Asked Answered
M

22

1438

I'm building a function to extend the Enum.Parse concept that

  • Allows a default value to be parsed in case that an Enum value is not found
  • Is case insensitive

So I wrote the following:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

I am getting a Error Constraint cannot be special class System.Enum.

Fair enough, but is there a workaround to allow a Generic Enum, or am I going to have to mimic the Parse function and pass a type as an attribute, which forces the ugly boxing requirement to your code.

EDIT All suggestions below have been greatly appreciated, thanks.

Have settled on (I've left the loop to maintain case insensitivity - I am using this when parsing XML)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

EDIT: (16th Feb 2015) Christopher Currens has posted a compiler enforced type-safe generic solution in MSIL or F# below, which is well worth a look, and an upvote. I will remove this edit if the solution bubbles further up the page.

EDIT 2: (13th Apr 2021) As this has now been addressed, and supported, since C# 7.3, I have changed the accepted answer, though full perusal of the top answers is worth it for academic, and historical, interest :)

Mosstrooper answered 17/9, 2008 at 1:56 Comment(18)
Maybe you should use ToUpperInvariant() instead of ToLower()...Tempi
Why are extension methods only for reference types?Marital
@Shimmy: As soon as you pass a value type to the extension method, you're working on a copy of it, so you can't change its state.Honeymoon
Know it is an old thread, don't know if they changed things, but extension methods works fine for value types, sure they might not always make as much sense, but I have used "public static TimeSpan Seconds(this int x) { return TimeSpan.FromSeconds(x); }" to enable the syntax of "Wait.For(5.Seconds())..."Pe
Realize this wasn't part of the question, but you could improve your foreach loop logic by using String.Equals with StringComparison.InvariantCultureIgnoreCaseBaggett
possible duplicate of Anyone know a good workaround for the lack of an enum generic constraint?Benignant
Why use that foreach-loop? Enum.Parse contains an ignoreCase-parameter (I guess since .Net 2.0).Whitesell
I went ahead and added an answer with the existing ignoreCase-parameter and a generic default value as optional arguments, and some more improvements suggested by others.Whitesell
Worth noting that Enum.Parse can handle an enum with the [Flags] attribute, provided values in the string are separated with commas.Grasmere
Please post your solution as an answer post. Don't include answers in questions.Bulla
How is the value string created? If this was created using the Enum.ToString() method, and the Enum type is marked with the [Flags] attribute, the defaultValue will always be returned from the method with value = (enum.type1 | enum.type2).ToString() == type1, type2. Corner case.Dorpat
check this answer written by me https://mcmap.net/q/22288/-how-do-i-convert-an-enum-between-string-list-of-flags-enum-with-flagsCroquet
@bigworld12 The initial requirement for me to do this is long lost in the mists of time, but very comprehensive solution nonetheless :)Mosstrooper
Implementing this functionality is on the drawing board for C# 7! Vote it up! github.com/dotnet/roslyn/issues/262Boxwood
Very old topic, but there's been a huge improvement since C# 7.3. It's now fully supported to use Enum constraints. See my longer answer all the way down at the bottom.Sportsman
NB: this feature is supported as of C# 7.3Whitesell
Despite the fact this works in c# 7.3 and upwards, it is somewhat silly that having nullable property of a type T with constraint where T: Enum the compiles still cries like a baby that T must be of a non-nullable type, resulting that in whole hierarchy of generics being passed one must specify struct constraint as well.Premium
Starting from .NET Core 2.0 (from 2017), the class library has generic overloads that allow you to write stuff like var val = Enum.Parse<DayOfWeek>("ThuRSday", ignoreCase: true);.Geologize
C
930

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);
Carlocarload answered 10/11, 2011 at 21:46 Comment(14)
Yeah, very hardcore. I have the utmost respect for someone who can code in IL, and know how the features are supported at the higher language level - a level which many of us still see as being low level under applications, business rules, UI's, component libraries, etc.Quarterphase
@ruslan - it says that you can't accomplish this in c# in the first paragraph of the answer. Thats actually what this answer is showing: very possible in cil (since the code above works successfully when used in other .net languages), however not possible in C# by itself.Carlocarload
What I'd really like to know is why the C# team hasn't started allowing this yet, since it is already supported by MSIL.Alfieri
@Alfieri - From Eric Lippert: There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.Carlocarload
Having worked with a mix of C and Assembler (various) for many years out of necessity, I can only say I stay away from those unmaintainable things. One thing I have never understood though, is WHY does the .NET team doesn't support this if MSIL has the capability of doing it and there are so many people asking for it and having to write some horrible workarounds.Jochbed
@LordofScripts: I think the reason is that that since a class which constrains a T to System.Enum wouldn't be able to do all the things with T that people might expect, the authors of C# figured they may as well forbid it altogether. I consider the decision unfortunate, since it C# had simply ignored any special handling of System.Enum constraints, it would have been possible to write a HasAnyFlags<T>(this T it, T other) extension method that was orders of magnitude faster than Enum.HasFlag(Enum) and which type-checked its arguments.Phosphoprotein
@MichaelBlackburn It's more complicated than it sounds, mostly due to bit flags on enums. A github user named HaloFour gives a good summary in this Roslyn issue.Carlocarload
Just my three cents.. Since System.Enum is too special to get sexy handling, and since we've already got where T:class, where T:struct, etc, then where T:enum seems a pretty badass and probably trivial to implement..Entremets
Despite the fact this works in c# 7.3 and upwards, it is somewhat silly that having nullable property of a type T with constraint where T: Enum the compiles still cries like a baby that T must be of a non-nullable type, resulting that in whole hierarchy of generics being passed one must specify struct constraint as well. Damn...Premium
Yes, it is better to say where T : struct, System.Enum because then the compiler will know it is always a value type. As @ThatMarc just said, it is needed if you want to use T? (Nullable<T>). It could also help you in other situations, like T t = …; if (t == null) { … }, where the compiler will give a helpful message if it knows T is a value type.Geologize
@JeppeStigNielsen but System.Enum inherits from System.ValueType, so the struct constraint adds nothing. Every type that inherits from System.Enum is non-nullable. This is either a bug or a design flaw.Condensable
@Condensable It may feel like a flaw, but it is still true that it is better to say where T : struct, System.Enum, instead of just where T : System.Enum. Formally, the difference is only the type System.Enum itself which is a reference type. For example, the method at the top of this answer (as it stands right now) could be invoked with EnumNamedValues<System.Enum>(); and the constraint would be met, and default(T) (inside the method) would be a true null reference.Geologize
@JeppeStigNielsen of course. Thanks for setting me straight. Though by analogy with System.ValueType and struct, they could have used the enum keyword for the constraint. Do you know why they didn't?Condensable
@Condensable No, I also wonder why not. It would have been nicer.Geologize
P
1104

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.

Philbrook answered 17/9, 2008 at 4:13 Comment(14)
this appears to only be vs2008 and newer, right? or maybe it's just not in vb2005?Andrej
Generics are available since .NET 2.0. Hence they are available in vb 2005 as well.Philbrook
Well, make it even more constrained then, if you choose to go down this path... use "class TestClass<T> where T : struct, IComparable, IFormattable, IConvertible"Koine
Another suggestion is to define the generic type with the identifier TEnum. Thus: public TEnum GetEnumFromString<TEnum>(string value) where TEnum : struct, IConvertible, IComparible, IFormattable { }Anzus
You don't gain much by including the other interfaces because almost all of the built-in value types implement all of those interfaces. This is especially true for constraints on a generic extension method, which is extremely handy for operating on enums, except for the fact that those extension methods are like a virus that infects all your objects. IConvertable at least narrows it down quite a bit.Treachery
Of course this accepted answer notes the best exception for genetic types is NotSupportedExection() and not ArgumentException().Esteban
doesn't quite get the compile-time checking that I'm looking forAindrea
@SamIam : When you posted, this thread was what, 6 and a half years old, and you were correct, no compile-time checking in any of the answers. Then only 3 days later, after 6 years, you got your wish - see Julien Lebosquain's post way below.Jeunesse
Indeed, David, as time has passed by and both the language and the combined community knowledge has evolved, we now have a better solution thanks to Julien Lebosquain. Downvoted this answer not because it is bad, but because Julien's answer is better and should surpass this one in votes. Please notify me if that's frowned upon.Foudroyant
@Anzus - there is a spelling error in your example which causes it not to work, I believe it should be IComparable not IComparible (a not i)Mayo
for .net core use typeof(T).GetTypeInfo().IsEnumBriarroot
@Mayo You're quite right. But I can't edit my comment it seems. Sorry.Anzus
Very old topic, but there's been a huge improvement since C# 7.3. It's now fully supported to use Enum constraints. See my longer answer all the way down at the bottom.Sportsman
This feature is supported as of C# 7.3Whitesell
C
930

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);
Carlocarload answered 10/11, 2011 at 21:46 Comment(14)
Yeah, very hardcore. I have the utmost respect for someone who can code in IL, and know how the features are supported at the higher language level - a level which many of us still see as being low level under applications, business rules, UI's, component libraries, etc.Quarterphase
@ruslan - it says that you can't accomplish this in c# in the first paragraph of the answer. Thats actually what this answer is showing: very possible in cil (since the code above works successfully when used in other .net languages), however not possible in C# by itself.Carlocarload
What I'd really like to know is why the C# team hasn't started allowing this yet, since it is already supported by MSIL.Alfieri
@Alfieri - From Eric Lippert: There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.Carlocarload
Having worked with a mix of C and Assembler (various) for many years out of necessity, I can only say I stay away from those unmaintainable things. One thing I have never understood though, is WHY does the .NET team doesn't support this if MSIL has the capability of doing it and there are so many people asking for it and having to write some horrible workarounds.Jochbed
@LordofScripts: I think the reason is that that since a class which constrains a T to System.Enum wouldn't be able to do all the things with T that people might expect, the authors of C# figured they may as well forbid it altogether. I consider the decision unfortunate, since it C# had simply ignored any special handling of System.Enum constraints, it would have been possible to write a HasAnyFlags<T>(this T it, T other) extension method that was orders of magnitude faster than Enum.HasFlag(Enum) and which type-checked its arguments.Phosphoprotein
@MichaelBlackburn It's more complicated than it sounds, mostly due to bit flags on enums. A github user named HaloFour gives a good summary in this Roslyn issue.Carlocarload
Just my three cents.. Since System.Enum is too special to get sexy handling, and since we've already got where T:class, where T:struct, etc, then where T:enum seems a pretty badass and probably trivial to implement..Entremets
Despite the fact this works in c# 7.3 and upwards, it is somewhat silly that having nullable property of a type T with constraint where T: Enum the compiles still cries like a baby that T must be of a non-nullable type, resulting that in whole hierarchy of generics being passed one must specify struct constraint as well. Damn...Premium
Yes, it is better to say where T : struct, System.Enum because then the compiler will know it is always a value type. As @ThatMarc just said, it is needed if you want to use T? (Nullable<T>). It could also help you in other situations, like T t = …; if (t == null) { … }, where the compiler will give a helpful message if it knows T is a value type.Geologize
@JeppeStigNielsen but System.Enum inherits from System.ValueType, so the struct constraint adds nothing. Every type that inherits from System.Enum is non-nullable. This is either a bug or a design flaw.Condensable
@Condensable It may feel like a flaw, but it is still true that it is better to say where T : struct, System.Enum, instead of just where T : System.Enum. Formally, the difference is only the type System.Enum itself which is a reference type. For example, the method at the top of this answer (as it stands right now) could be invoked with EnumNamedValues<System.Enum>(); and the constraint would be met, and default(T) (inside the method) would be a true null reference.Geologize
@JeppeStigNielsen of course. Thanks for setting me straight. Though by analogy with System.ValueType and struct, they could have used the enum keyword for the constraint. Do you know why they didn't?Condensable
@Condensable No, I also wonder why not. It would have been nicer.Geologize
B
269

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.

Brittenybrittingham answered 15/2, 2015 at 15:16 Comment(15)
Also, can you elaborate on your answer a bit. The comment you include from the language spec tells us your Enum type has to be System.Object, System.FalueType, System.Enum or any interface. How is it further restricted to just the System.Enum type? Wouldn't you need to do public class EnumUtils : EnumClassUtils<Enum> where Enum : struct, IConvertible? thanks.Jeunesse
@DavidI.McIntosh EnumClassUtils<System.Enum> is sufficient to restrict T to any System.Enum and any derived types. struct on Parse then restricts it further to a real enum type. You need to restrict to Enum at some point. To do so, your class has to be nested. See gist.github.com/MrJul/7da12f5f2d6c69f03d79Brittenybrittingham
Ah, I see how it works now. Then use of the class must always be via reference to it as a nested class - not pleasant, but I guess that's the best that can be hoped for. Thanks for the prompt reply by the way.Jeunesse
Just to be clear, my comment "not pleasant" was not a comment on your solution - it is really a beautiful hack. Just "not pleasant" that MS forces us to use such a convoluted hack.Jeunesse
Is there a way to work this to also be useable for extension methods?Spake
@Max Unfortunately, I don't think so :(Brittenybrittingham
I would add an internal constructor, to prevent this class from being inherited in strange ways.Ovular
Wouldn't it be nice if you could do this: where T : class, structTrammel
What does the where TClass : class constraint gain here?Celtuce
Is there a way to further constrain TEnum so that int v; TEnum e = (TEnum) v; is allowed?Megillah
It's about time! And it BETTER support int v; TEnum e = (TEnum)v;, that is to say it better be smart enough to realize all Enums are ints! Don't **** it up Microsoft!Serranid
@Trinkyo enum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }Transarctic
Despite the fact this works in c# 7.3 and upwards, it is somewhat silly that having nullable property of a type T with constraint where T: Enum the compiles still cries like a baby that T must be of a non-nullable type, resulting that in whole hierarchy of generics being passed one must specify struct constraint as well. Damn...Premium
@tsemer: The where TClass : class is needed to avoid that you can write EnumUtils.Parse<int>("value"); for example.Stockwell
this is the only answer that worked for me under .NET 8 in a situation in which I need to use Enum.Parse<> which didn't allow me to reference the generic type I was restricting only to Enum complaining about it being nullable - adding the struct restriction did away with that.Oriana
A
36

The existing answers are true as of C# <=7.2. However, there is a C# language feature request (tied to a corefx feature request) to allow the following;

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

At time of writing, the feature is "In discussion" at the Language Development Meetings.

EDIT

As per nawfal's info, this is being introduced in C# 7.3.

EDIT 2

This is now in C# 7.3 forward (release notes)

Sample;

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;
}
Asteriated answered 22/3, 2018 at 15:27 Comment(5)
Interesting discussion there, thanks. Nothing set in stone yet though (as yet)Mosstrooper
@johnc, very true but worth a note and it is a frequently asked feature. Fair odds on it coming in.Asteriated
This is coming in C# 7.3: learn.microsoft.com/en-us/visualstudio/releasenotes/…. :)Benignant
Please upvote this answer, it should be much much higher in the list here, now that the feature exists! :)Absinthe
I' m currently using C# 11. AFAIK, only "where LT : struct, Enum" does work.Forsworn
W
35

Edit

The question has now superbly been answered by Julien Lebosquain. I would also like to extend his answer with ignoreCase, defaultValue and optional arguments, while adding TryParse and ParseOrDefault.

public abstract class ConstrainedEnumParser<TClass> where TClass : class
// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]
{
    // internal constructor, to prevent this class from being inherited outside this code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        var didParse = Enum.TryParse(value, ignoreCase, out result);
        if (didParse == false)
        {
            result = defaultValue;
        }
        return didParse;
    }
    public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum result;
        if (Enum.TryParse(value, ignoreCase, out result)) { return result; }
        return defaultValue;
    }
}

public class EnumUtils: ConstrainedEnumParser<System.Enum>
// reference type constraint to any <System.Enum>
{
    // call to parse will then contain constraint to specific <System.Enum>-class
}

Examples of usage:

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);
WeekDay parsedDayOrDefault;
bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);
parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

Old

My old improvements on Vivek's answer by using the comments and 'new' developments:

  • use TEnum for clarity for users
  • add more interface-constraints for additional constraint-checking
  • let TryParse handle ignoreCase with the existing parameter (introduced in VS2010/.Net 4)
  • optionally use the generic default value (introduced in VS2005/.Net 2)
  • use optional arguments(introduced in VS2010/.Net 4) with default values, for defaultValue and ignoreCase

resulting in:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}
Whitesell answered 24/5, 2013 at 14:7 Comment(0)
E
20

You can define a static constructor for the class that will check that the type T is an enum and throw an exception if it is not. This is the method mentioned by Jeffery Richter in his book CLR via C#.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Then in the parse method, you can just use Enum.Parse(typeof(T), input, true) to convert from string to the enum. The last true parameter is for ignoring case of the input.

Elisabethelisabethville answered 17/9, 2008 at 2:32 Comment(3)
This is a good option for generic classes -- but of course, it doesn't help for generic methods.Cirillo
Also, this is also not enforced at compile time, you would only know you provided a non Enum T when the constructor executed. Though this is much nicer than waiting for an instance constructor.Edd
@Cirillo a generic method (such as an extension method) can use a generic helper class to apply this pattern. This at least guarantees that the type will be tested only once. But of course it's still not enforced at compile time.Condensable
S
20

It should also be considered that since the release of C# 7.3 using Enum constraints is supported out-of-the-box without having to do additional checking and stuff.

So going forward and given you've changed the language version of your project to C# 7.3 the following code is going to work perfectly fine:

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
    {
        // Your code goes here...
    }

In case you're don't know how to change the language version to C# 7.3 see the following screenshot: enter image description here

EDIT 1 - Required Visual Studio Version and considering ReSharper

For Visual Studio to recognize the new syntax you need at least version 15.7. You can find that also mentioned in Microsoft's release notes, see Visual Studio 2017 15.7 Release Notes. Thanks @MohamedElshawaf for pointing out this valid question.

Pls also note that in my case ReSharper 2018.1 as of writing this EDIT does not yet support C# 7.3. Having ReSharper activated it highlights the Enum constraint as an error telling me Cannot use 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' as type parameter constraint. ReSharper suggests as a quick fix to Remove 'Enum' constraint of type paramter T of method

However, if you turn off ReSharper temporarily under Tools -> Options -> ReSharper Ultimate -> General you'll see that the syntax is perfectly fine given that you use VS 15.7 or higher and C# 7.3 or higher.

Sportsman answered 10/5, 2018 at 9:55 Comment(6)
What VS version are you using?Kennethkennett
@MohamedElshawaf I believe it's version 15.7 that contains support for C# 7.3Glycerite
I think it's better to write where T : struct, Enum, to avoid passing System.Enum itself as type parameter.Heighho
Like @MariuszPawelski I write struct, Enum. My rationale is explained in the answer and comments here.Cooney
The ReSharper info really helped me. Note latest preview version supports this feature.Imagine
I'm surprised it took this longAindrea
C
11

I modified the sample by dimarzionist. This version will only work with Enums and not let structs get through.

public static T ParseEnum<T>(string enumString)
    where T : struct // enum 
    {
    if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)
       throw new Exception("Type given must be an Enum");
    try
    {

       return (T)Enum.Parse(typeof(T), enumString, true);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}
Colostomy answered 17/9, 2008 at 2:24 Comment(2)
I wouldn't return the default value on failure; I'd let the exception propagate (just as it does with Enum.Parse). Instead, use TryParse returning a bool and return the result using an out param.Alexipharmic
OP wants it to be case-insensitive, this is not.Revetment
O
11

I tried to improve the code a bit:

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible
{
    if (Enum.IsDefined(typeof(T), value))
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
    return defaultValue;
}
Outer answered 16/12, 2010 at 11:24 Comment(2)
This is better than the accepted answer because it allows you to call defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo) even though you don't know which type of enum it is, only that the object is an enum.Determine
The advance check with IsDefined will ruin the case insensitivity, though. Unlike Parse, IsDefined has no ignoreCase argument, and MSDN says it only matches exact case.Setser
W
10

note that System.Enum Parse() & TryParse() methods still have where struct constraints rather than where Enum, so that this won't compile:

    bool IsValid<TE>(string attempted) where TE : Enum
    {
        return Enum.TryParse(attempted, out TE _);
    }

but this will:

bool Ok<TE>(string attempted) where TE : struct,Enum
{
    return Enum.TryParse(attempted, out var _)
}

as a result, where struct,Enum may be preferable to just where Enum

Wilona answered 14/10, 2021 at 15:44 Comment(0)
C
6

Hope this is helpful:

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)
                  where TValue : struct // enum 
{
      try
      {
            if (String.IsNullOrEmpty(value))
                  return defaultValue;
            return (TValue)Enum.Parse(typeof (TValue), value);
      }
      catch(Exception ex)
      {
            return defaultValue;
      }
}
Cychosz answered 17/9, 2008 at 2:0 Comment(1)
If you need case insensitivity, simply replace return (TValue)Enum.Parse(typeof (TValue), value); by return (TValue)Enum.Parse(typeof (TValue), value, true);Unspotted
I
6

I do have specific requirement where I required to use enum with text associated with enum value. For example when I use enum to specify error type it required to describe error details.

public static class XmlEnumExtension
{
    public static string ReadXmlEnumAttribute(this Enum value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);
        return attribs.Length > 0 ? attribs[0].Name : value.ToString();
    }

    public static T ParseXmlEnumAttribute<T>(this string str)
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);
            if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;
        }
        return (T)Enum.Parse(typeof(T), str, true);
    }
}

public enum MyEnum
{
    [XmlEnum("First Value")]
    One,
    [XmlEnum("Second Value")]
    Two,
    Three
}

 static void Main()
 {
    // Parsing from XmlEnum attribute
    var str = "Second Value";
    var me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    // Parsing without XmlEnum
    str = "Three";
    me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    me = MyEnum.One;
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
}
Inveigle answered 18/4, 2010 at 5:38 Comment(0)
N
3

Interestingly enough, apparently this is possible in other langauges (Managed C++, IL directly).

To Quote:

... Both constraints actually produce valid IL and can also be consumed by C# if written in another language (you can declare those constraints in managed C++ or in IL).

Who knows

Nitid answered 7/7, 2009 at 17:0 Comment(1)
Managed Extensions for C++ don't have ANY support for generics, I think you mean C++/CLI.Burbage
F
3

This is my take at it. Combined from the answers and MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

MSDN Source

Fauteuil answered 25/3, 2014 at 13:40 Comment(1)
This doesn't really make sense. If TEnum actually is an Enum type but text is an empty string then you get an ArgumentException saying "TEnum must be an Enum type" even though it is.Ezmeralda
K
2

I always liked this (you could modify as appropriate):

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}
Klara answered 4/10, 2010 at 23:9 Comment(0)
C
2

I created an extension Method to get integer value from enum take look at method implementation

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

this is usage

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way
Civilize answered 1/11, 2016 at 9:29 Comment(2)
While it probably works, it has almost no relevance to the question.Entremets
nontheless this is what I was looking for .)Amie
H
1

I loved Christopher Currens's solution using IL but for those who don't want to deal with tricky business of including MSIL into their build process I wrote similar function in C#.

Please note though that you can't use generic restriction like where T : Enum because Enum is special type. Therefore I have to check if given generic type is really enum.

My function is:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}
Hyonhyoscine answered 23/4, 2012 at 1:23 Comment(0)
D
1

I've encapsulated Vivek's solution into a utility class that you can reuse. Please note that you still should define type constraints "where T : struct, IConvertible" on your type.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}
Deuced answered 25/7, 2013 at 7:55 Comment(0)
B
1

As stated in other answers before; while this cannot be expressed in source-code it can actually be done on IL Level. @Christopher Currens answer shows how the IL do to that.

With Fodys Add-In ExtraConstraints.Fody there's a very simple way, complete with build-tooling, to achieve this. Just add their nuget packages (Fody, ExtraConstraints.Fody) to your project and add the constraints as follows (Excerpt from the Readme of ExtraConstraints):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

and Fody will add the necessary IL for the constraint to be present. Also note the additional feature of constraining delegates:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

Regarding Enums, you might also want to take note of the highly interesting Enums.NET.

Biscuit answered 24/7, 2017 at 6:49 Comment(0)
R
1

This is my implementation. Basically, you can setup any attribute and it works.

public static class EnumExtensions
    {
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            FieldInfo fi = type.GetField(@enum.ToString());
            DescriptionAttribute[] attrs =
                fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
            if (attrs.Length > 0)
            {
                return attrs[0].Description;
            }
            return null;
        }
    }
Richelle answered 6/11, 2019 at 13:29 Comment(0)
F
0

If it's ok to use direct casting afterwards, I guess you can use the System.Enum base class in your method, wherever necessary. You just need to replace the type parameters carefully. So the method implementation would be like:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Then you can use it like:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);
Flaunch answered 23/11, 2017 at 14:17 Comment(1)
use of Enum.ToObject() would yield a more flexible result. Added to which, you could do the string comparisons without case sensitivity which would negate the need to call ToLower()Asteriated
P
-8

Just for completeness, the following is a Java solution. I am certain the same could be done in C# as well. It avoids having to specify the type anywhere in code - instead, you specify it in the strings you are trying to parse.

The problem is that there isn't any way to know which enumeration the String might match - so the answer is to solve that problem.

Instead of accepting just the string value, accept a String that has both the enumeration and the value in the form "enumeration.value". Working code is below - requires Java 1.8 or later. This would also make the XML more precise as in you would see something like color="Color.red" instead of just color="red".

You would call the acceptEnumeratedValue() method with a string containing the enum name dot value name.

The method returns the formal enumerated value.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
Piccaninny answered 30/5, 2018 at 4:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.