Wednesday, May 7, 2008

Nullable enums

Enums in .net are represented as integers, right? This means you can do the following (yes, casting the integer to object is superfluous. It is added for clarity):
public enum Foo { X, Y };
[...]
public Foo Bar { set { ... } }
[...]
PropertyInfo prop = obj.GetType().GetProperty("Bar");
prop.SetValue(obj, (object)1, null);

I.e. you can use reflection to set the value of a property of an enum type using its integer value. (You could also just cast it like so: obj.Bar = (Foo)1;, but that's not quite as interesting, for reasons that will be revealed later.)

Also, a boxed value and a boxed nullable value have the same representation, right? They are both pointers to a small object containing the primitive. As the following code shows, boxed integers and boxed nullable integers are interchangeable:

int intValue = 1;
int? nullableIntValue = 1;
object boxedInt = intValue;
object boxedNullableInt = nullableIntValue;
intValue = (int)boxedNullableInt;
nullableIntValue = (int?)boxedInt;

And, not surprisingly, the same holds for enum values, now demonstrated using reflection as in our first example:

public Foo? Bar { set { ... } }
[...]
prop.SetValue(obj, (object)Foo.X, null);

Now, combining these two examples, what do you think the following code would do?

public Foo? Bar { set { ... } }
[...]
prop.SetValue(obj, (object)1, null);

If you, like me, thought it would happily set the property to the enum value correspoding to 1, in this case Foo.X, you'd be wrong. Instead it throws the following exception:

Object of type 'System.Int32' cannot be converted to type 'System.Nullable`1

Huh? The .net designers may know why this is, and they may even have a good reason for it, but to me it just seems plain weird. It is worth noting that obj.Bar = (Foo?)1; works just fine.

This issue came up when mapping objects from database tables, using integers to represent enum values. I've reluctantly added the following workaround to my mapping code:

PropertyInfo prop = [...]
object val = [...]
if ((prop.PropertyType.IsGenericType) &&
(prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
Type targetType = prop.PropertyType.GetGenericArguments()[0];
if (targetType.IsEnum)
val = Enum.ToObject(targetType, val);
}
prop.SetValue(obj, val, null);

No comments: