I'm building a function to extend the Enum.Parse
concept that
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 :)
The enum is a default subclass of the generic Enum<T> class, where T represents generic enum type. This is the common base class of all Java language enumeration types. The transformation from enum to a class is done by the Java compiler during compilation.
Enum Class in JavaAn enum class can include methods and fields just like regular classes. When we create an enum class, the compiler will create instances (objects) of each enum constants.
A change in the default value of an enum member will automatically assign incremental values to the other members sequentially. You can even assign different values to each member. The enum can be of any numeric data type such as byte, sbyte, short, ushort, int, uint, long, or ulong.
Answers. Yes, you can easily define a enumeration extending existing enum.
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.
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.
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With