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 Nullableso 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.