I had an interesting challenge yesterday that involved converting a string to an enumerated value. This seems like it would be really trivial, but I had a few extra considerations that made it harder to deal with.
We're all familiar with the Enum.Parse method that has existed in the .NET Framework since version 1.1. It's a somewhat clunky and verbose method:
MyEnum result = (MyEnum)Enum.Parse(typeof(MyEnum), "First");
Recently, the .NET Framework 4.0 introduced a new static method on the Enum type which uses generics. It's much cleaner, if you don't mind the out parameters.
MyEnum result;
if (Enum.TryParse("First", out result))
{
// do something with 'result'
}
Unfortunately, the above methods are constrained to work with struct value-types only and won’t work with Nullable types. After some experimenting and fussing with casting between generic types I managed to get a solution that works with both standard and nullable enumerations.
I’m sure there’s a bit extra boxing in here, so as always your feedback is welcome. Otherwise, this free-as-in-beer extension method is going on my tool belt.
public static TResult ConvertToEnum<TResult>(this string source)
{
// we can't get our values out of a Nullable so we need
// to get the underlying type
Type enumType = GetUnderlyingTypeIfNullable(typeof(TResult));
// unfortunatetly, .net 4.0 doesn't have constraints for Enum
if (!enumType.IsEnum)
throw new NotSupportedException("Only enums can be converted here, chum.");
if (!String.IsNullOrEmpty(source))
{
object rawValue = GetRawEnumValueFromString(enumType, source);
// if there was a match
if (rawValue != null)
{
// having the value isn't enough, we need to
// convert this back to an enum so that we can
// perform an implicit cast back to the generic type
object enumValue = Enum.Parse(enumType, rawValue.ToString());
// implicit cast back to generic type
// if the generic type was nullable, the cast
// from non-nullable to nullable is also implicit
return (TResult)enumValue;
}
}
// if no original value, or no match use the default value.
// returns 0 for enum, null for nullable<enum>
return default(TResult);
}
private static Type GetUnderlyingTypeIfNullable(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
// Nullable<T> only has one type argument
return type.GetGenericArguments().First();
}
return targetType;
}
private static object GetRawEnumValueFromString(Type enumType, string source)
{
FieldInfo[] fields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
// attempt to find our string value
foreach (FieldInfo field in fields)
{
object value = field.GetRawConstantValue();
// exact match
if (field.Name == source)
{
return value;
}
// attempt to locate in attributes
var attribs = field.GetCustomAttributes(typeof (XmlEnumAttribute), false);
{
if (attribs.Cast<XmlEnumAttribute>().Any(attrib => source == attrib.Name))
{
return value;
}
}
}
return null;
}
Heh: “Free as in Beer”. Happy St. Patty’s, everybody.