tags:

views:

8593

answers:

12

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 usng 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;
    }
}
+11  A: 

You can constrain a generic type parameter to be a value type (such as an int, a bool, and enum) or any custom structure using the struct constraint:

public class MyClass<T> where T : struct
{
   //...
}
Nescio
VB Alterative would be Public Class MyClass<T as struct>
Tom Anderson
In your so-short comment you managed to make 3 errors: 1) MyClass is a keyword in VB. 2) (Of T) is used for g. type params in VB, not <>. 3) A 'struct' in VB is 'Structure' not 'struct' as in C#. VB alternative is Public Class [MyClass](Of T As Structure) (the brackets are to tell the compiler to treat ignore keywords - not recommended, the best is use a different class name i.e. Public Class StructureWrapper(Of T As Structure) etc.).
Shimmy
+2  A: 

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;
                  }
            }
dimarzionist
Nicely cleaned, thanks
johnc
Thanks, but, this does not address the case insensitive functionality I wanted.
johnc
If you need case insensitivity, simply replace `return (TValue)Enum.Parse(typeof (TValue), value);` by `return (TValue)Enum.Parse(typeof (TValue), value, true);`
Paulo Santos
+4  A: 

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);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}
Bivoauc
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.
Mark Simpson
+3  A: 

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.

Karg
I was unaware of the case insensitive option on Enum.Parse, thanks
johnc
+28  A: 

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.

Vivek
this appears to only be vs2008 and newer, right? or maybe it's just not in vb2005?
Maslow
Generics are available since .NET 2.0. Hence they are available in vb 2005 as well.
Vivek
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"
Ricardo Nolde
+1  A: 

Maybe you should use ToUpperInvariant() instead of ToLower()...

Yacoder
Thanks, I never knew that
johnc
+1  A: 

LagerDalek, running your edited code through ildasm actually creates a single unbox instruction in the foreach loop. FxCop happened to catch it for me. :)

interesting ...
johnc
+1  A: 

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

Andrew Backer
A: 

I have one more solution for this.

        private T GetEnum<T>(string str, T defaultValue)
        {
            T outPutValue = defaultValue;
            outPutValue = (T)Enum.Parse(typeof(T), str, true);
            return outPutValue;
        }
Parvendra Rana
Thanks, but fails if enum is not found. You could wrap it in a try / catch clause, but not particularly elegant.Failing code: enum Test { One, Two, Three} public void ThisWillCauseException() { WL(GetEnum<Test>("Four", Test.One)); RL(); }
johnc
Also doesn't answer the question
johnc
A: 

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());
}
Sunny Rajwadi
A: 

Generic Method – How to specify type constrain on T for enum type

http://patelshailesh.com/index.php/generic-method-how-to-specify-type-constrain-on-t-for-enum-type

shailesh
+1  A: 

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>();
}
Jeff
Nice ... thanks
johnc