13 September 2011

Fun with enum

If you’ve done any vaguely serious programming with a pre-4 version of the .NET Framework then chances are you’ve had to write an Enum.TryParse() method. You probably wrote something like this:

public static bool TryParse<TEnum>(string value, out TEnum enumValue)
{
 Type enumType = typeof(TEnum);
 if (!enumType.IsEnum) throw new ArgumentException("Type is not an enum.");
 
 enumValue = default(TEnum);
 
 if (Enum.IsDefined(enumType, value))
 {
  enumValue = (TEnum)Enum.Parse(enumType, value);
  return true;
 }
 
 return false;
}

Everything went fine until someone decided to pass in a string representing a value of the underlying type such as “0” at which point Enum.IsDefined() said no even though your enum looked like this:

public enum MyEnum
{
 Zero = 0, One, Two, Three
}

Enum.Parse() will accept “0” just fine but IsDefined() requires the value be of the correct underlying type so in this case you’d need 0 as an integer for it to return true. Doesn't that mean I now need to work out the underlying type and then do the appropriate Parse() method using reflection? Oh dear, looks like our nice generic solution may get rather complicated!

Fear not. Because we know our input type is a string and there are a very limited number of underlying types we can have there’s a handy framework method we can use to sort this out – Convert.ChangeType().

public static bool IsUnderlyingDefined(Type enumType, string value)
{
 if (!enumType.IsEnum) throw new ArgumentException("Type is not an enum.");
 
 Type underlying = Enum.GetUnderlyingType(enumType);
 
 var val = Convert.ChangeType(value, underlying, CultureInfo.InvariantCulture);
  
 return Enum.IsDefined(enumType, val);
}

ChangeType() is effectively selecting the correct Parse method for us and calling it, passing in our string and returning a nice strongly typed underlying value which we can pass into Enum.IsDefined(). So our TryParse now looks like this:

public static bool TryParse<TEnum>(string value, out TEnum enumValue)
{
 Type enumType = typeof(TEnum);
 if (!enumType.IsEnum) throw new ArgumentException("Type is not an enum.");
 
 enumValue = default(TEnum);
 
 if (Enum.IsDefined(enumType, value) || IsUnderlyingDefined(enumType, value))
 {
  enumValue = (TEnum)Enum.Parse(enumType, value);
  return true;
 }
 
 return false;
}

This exercise is somewhat contrived especially now Enum.TryParse is part of .NET 4.0 but the synergy of ChangeType and IsDefined is quite nice and a technique worth pointing out nonetheless.

Links

No comments: