Domanda

Ho fatto il seguente metodo di estensione ...

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

... che uso per esempio lettura dei dati in questo modo:

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

Ma non funziona quando voglio cast un enum annullabile da un valore in scatola. Il meglio che ho potuto venire con era presente (disordinato WIP codice che non funziona):

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

Utilizzo:

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

L'errore corrente è un InvalidCastException quando tenta di lanciare un Int32 al enum annullabile. Che è ok immagino, ma non ho idea di come altro potrei farlo? Ho cercato di creare un'istanza della enum nullable T e assegnarle un valore, ma mi sono bloccato su come esattamente questo può essere fatto.

Chiunque un'idea o un modo migliore per risolvere questo problema? E 'anche possibile per risolvere questo in modo generico? Ho fatto un sacco di ricerche su questo, ma non ho trovato niente di utile.

È stato utile?

Soluzione

È possibile farlo invocando il costruttore per il tipo di nullable è necessario. In questo modo:

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

Altri suggerimenti

Con risposta Hans' sono stato in grado di farlo funzionare e se qualcuno è interessato ecco la versione fissa:

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

Ma la verifica / codice aggiuntivo per l'enumerazione nullable fa male le prestazioni per tutti gli altri tipi. Prima che il metodo di estensione era ~ 2-3 lento, ora è ~ 10-15 volte. Farlo 1000000 (milioni) volte utilizzando il codice di cui sopra:

int Unboxing: 4ms
int Unboxing con metodo di estensione: 59ms (prima senza prendersi cura di enum annullabile: 12ms)
Unboxing a annullabile enum: 5ms
Unboxing per enum annullabile con metodo di estensione: 3382ms

Così, guardando questi numeri questi metodi non dovrebbe essere la prima scelta quando le prestazioni sono critiche -. Almeno non quando lo si utilizza per le enumerazioni Null

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top