Pregunta

He hecho el siguiente método de extensión ...

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;
    }
}

... que utilizo para, por ejemplo, la lectura de datos de esta manera:

string field = datareader["column"].As("default value when null")

Pero no funciona cuando quiero fundido a una enumeración anulable desde un valor en caja. Lo mejor que podía llegar a esta era (WIP desordenado código que no funciona):

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;
    }
}

Uso:

public enum SomeEnum {Value1, Value2};
object value = 1;
var result = value.As<SomeEnum?>();

El error actual es un InvalidCastException cuando se trata de emitir un Int32 a la enumeración anulable. Lo cual está bien supongo que, pero no tengo idea de qué otra manera podría hacer eso? He tratado de crear una instancia de la enumeración anulable T y asignarle un valor, pero estoy atascado sobre cómo exactamente esto se puede hacer.

Cualquier persona una idea o una mejor manera de resolver esto? ¿Es incluso posible resolver eso de una manera genérica? He hecho un montón de búsqueda en eso, pero no he encontrado nada útil.

¿Fue útil?

Solución

Puede hacerlo mediante la invocación del constructor para el tipo anulable que necesita. De esta manera:

            Type t = typeof(Nullable<>).MakeGenericType(lEnumType);
            var ctor = t.GetConstructor(new Type[] { lEnumType });
            return (T)ctor.Invoke(new object[] { pObject });

Otros consejos

Con la respuesta de Hans yo era capaz de conseguir que funcione y si a alguien le interesa aquí está la versión fija:

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;
    }
}

Pero la cheques / código adicional para la enumeración anulable duele el rendimiento de todos los otros tipos. Antes de que el método de extensión fue de ~ 2-3 lento, ahora es ~ 10-15 veces. Hacerlo 1000000 (millones) veces utilizando el código anterior:

int Unboxing: 4 ms
int unboxing utilizando el método de extensión: 59 ms (antes y sin el cuidado de enumeración anulable: 12 ms)
Unboxing a anulable enumeración: 5 ms
Unboxing a enum anulable utilizando el método de extensión: 3382ms

Así que, mirando a estos números de estos métodos no debería ser la primera opción cuando el rendimiento es crítico -. Al menos no cuando se utiliza por las enumeraciones anulables

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top