Pergunta

Eu fiz o seguinte método de extensão ...

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 eu uso para por exemplo, dados de leitura como assim:

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

Mas não funciona quando eu quero lançar para uma enumeração anulável a partir de um valor em caixa. O melhor que eu poderia inventar foi esse (código WIP bagunçado que não 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?>();

O erro atual é um invalidcastException quando tenta lançar um int32 no enumeração anulável. O que é ok, eu acho, mas não tenho ideia de que outra forma eu poderia fazer isso? Tentei criar uma instância do enum Nullável e atribuir um valor a ele, mas estou preso sobre como exatamente isso pode ser feito.

Alguém é uma ideia ou uma maneira melhor de resolver isso? É possível resolver isso de maneira genérica? Eu fiz muita pesquisa nisso, mas não achei nada útil.

Foi útil?

Solução

Você pode fazê -lo invocando o construtor para o tipo anulado necessário. Assim:

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

Outras dicas

Com a resposta de Hans, consegui fazê -lo funcionar e se alguém estiver interessado, aqui está a versão fixa:

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

Mas as verificações/código adicionais para o enum nulo prejudicam o desempenho de todos os outros tipos. Antes do método de extensão ser ~ 2-3 mais lento, agora é ~ 10-15 vezes. Fazendo isso 1000000 (milhões) vezes usando o código acima:

Unboxing Int: 4ms
Unboxing int usando o método de extensão: 59ms (antes sem cuidar de enum Nullable: 12ms)
Unboxing to Nullable Enum: 5ms
Unboxing para enum nullável usando o método de extensão: 3382ms

Portanto, analisar esses números esses métodos não deve ser a primeira opção quando o desempenho é crítico - pelo menos não quando o usá para enumes anuláveis.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top