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) Julien Lebosquain has recently 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.
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 it use:
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);
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.
- Christopher CurrensT
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. - supercat
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 { ... }
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.
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/7da12f5f2d6c69f03d79 - Julien Lebosquainwhere TClass : class
constraint gain here? - tsemer
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:
TEnum
for clarity for usersTryParse
handle ignoreCase
with the existing parameter
(introduced in VS2010/.Net 4) default
value (introduced in VS2005/.Net 2)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;
}
}
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.
Enum
T
when the constructor executed. Though this is much nicer than waiting for an instance constructor. - jrh
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);
}
}
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;
}
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. - styfleIsDefined
will ruin the case insensitivity, though. Unlike Parse
, IsDefined
has no ignoreCase
argument, and MSDN says it only matches exact case. - Nyerguds
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:
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.
where T : struct, Enum
, to avoid passing System.Enum
itself as type parameter. - Mariusz Pawelskistruct, Enum
. My rationale is explained in the answer and comments here. - Stephen Kennedy
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());
}
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;
}
}
return (TValue)Enum.Parse(typeof (TValue), value);
by return (TValue)Enum.Parse(typeof (TValue), value, true);
- Paulo Santos
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
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));
}
}
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. - Nick
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.
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>();
}
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;
}
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);
}
}
}
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
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.
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);
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()
- DiskJunky
In java, you would use...
SomeClass<T extends enum> {
}
Pretty straightforward, that.