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.

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top