Generic method, unboxing nullable enum
Question
I've made the following extension method ...
public static class ObjectExtensions
{
public static T As<T>(this object pObject, T pDefaultValue)
{
if (pObject == null || pObject == DBNull.Value)
return pDefaultValue;
return (T) pObject;
}
}
... which i use for e.g. reading data like so:
string field = datareader["column"].As("default value when null")
But it doesn't work when i want to cast to a nullable enum from a boxed value. The best i could come up with was this (messy WIP code which doesn't work):
public static class ObjectExtensions
{
public static T As<T>(this object pObject, T pDefaultValue)
{
if (pObject == null || pObject == DBNull.Value)
return pDefaultValue;
var lType = typeof (T);
if (!IsNullableEnum(lType))
return (T) pObject;
var lEnumType = Nullable.GetUnderlyingType(lType);
var lEnumPrimitiveType = lEnumType.GetEnumUnderlyingType();
if (lEnumPrimitiveType == typeof(int))
{
var lObject = (int?) pObject;
return (T) Convert.ChangeType(lObject, lType);
}
throw new InvalidCastException();
}
private static bool IsNullableEnum(Type pType)
{
Type lUnderlyingType = Nullable.GetUnderlyingType(pType);
return (lUnderlyingType != null) && lUnderlyingType.IsEnum;
}
}
Usage:
public enum SomeEnum {Value1, Value2};
object value = 1;
var result = value.As<SomeEnum?>();
The current error is an InvalidCastException when it tries to cast an Int32 to the nullable enum. Which is ok i guess, but i've no idea how else i could do that? I've tried to create an instance of the nullable enum T and assign it a value, but i'm stuck on how exactly this can be done.
Anyone an idea or a better way to solve this? Is it even possible to solve that in a generic way? I've done quite a lot of searching on that, but i've not found anything useful.
Solution
You can do it by invoking the constructor for the nullable type you need. Like this:
Type t = typeof(Nullable<>).MakeGenericType(lEnumType);
var ctor = t.GetConstructor(new Type[] { lEnumType });
return (T)ctor.Invoke(new object[] { pObject });
OTHER TIPS
With Hans' answer i was able to get it working and if anyone is interested here's the fixed version:
public static class ObjectExtensions
{
private static Dictionary<Type, ConstructorInfo> _NullableEnumCtor = new Dictionary<Type, ConstructorInfo>();
public static T As<T>(this object pObject)
{
return As(pObject, default(T));
}
public static T As<T>(this object pObject, T pDefaultValue)
{
if (pObject == null || pObject == DBNull.Value)
return pDefaultValue;
var lObjectType = pObject.GetType();
var lTargetType = typeof(T);
if (lObjectType == lTargetType)
return (T) pObject;
var lCtor = GetNullableEnumCtor(lTargetType);
if (lCtor == null)
return (T) pObject;
return (T)lCtor.Invoke(new[] { pObject });
}
private static ConstructorInfo GetNullableEnumCtor(Type pType)
{
if (_NullableEnumCtor.ContainsKey(pType))
return _NullableEnumCtor[pType];
var lUnderlyingType = Nullable.GetUnderlyingType(pType);
if (lUnderlyingType == null || !lUnderlyingType.IsEnum)
{
lock (_NullableEnumCtor) { _NullableEnumCtor.Add(pType, null); }
return null;
}
var lNullableType = typeof(Nullable<>).MakeGenericType(lUnderlyingType);
var lCtor = lNullableType.GetConstructor(new[] { lUnderlyingType });
lock (_NullableEnumCtor) { _NullableEnumCtor.Add(pType, lCtor); }
return lCtor;
}
}
But the additional checks/code for the nullable enum hurts performance for all other types. Before the extension method was ~2-3 slower, now it's ~10-15 times. Doing it 1000000 (million) times using the code above:
Unboxing int: 4ms
Unboxing int using extension method: 59ms (before without taking care of nullable enum: 12ms)
Unboxing to nullable enum: 5ms
Unboxing to nullable enum using extension method: 3382ms
So, looking at these numbers these methods shouldn't be the first choice when performance is critical - at least not when using it for nullable enums.