Общий метод, распаковка nullable enum - PullRequest
3 голосов
/ 30 августа 2010

Я сделал следующий метод расширения ...

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

... который я использую, например, для чтение данных примерно так:

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

Но это не работает, когда я хочу привести к обнуляемому перечислению из упакованного значения. Лучшее, что я мог придумать, было это (грязный код WIP, который не работает):

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

Использование:

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

Текущая ошибка InvalidCastException, когда он пытается преобразовать Int32 в обнуляемое перечисление. Думаю, это нормально, но я понятия не имею, как еще я могу это сделать? Я пытался создать экземпляр обнуляемого перечисления T и присвоить ему значение, но я застрял на том, как именно это можно сделать.

У кого-нибудь есть идея или лучший способ решить эту проблему? Можно ли вообще это решить? Я довольно много искал по этому поводу, но ничего полезного не нашел.

Ответы [ 2 ]

3 голосов
/ 30 августа 2010

Вы можете сделать это, вызвав конструктор для нужного типа NULL.Как это:

            Type t = typeof(Nullable<>).MakeGenericType(lEnumType);
            var ctor = t.GetConstructor(new Type[] { lEnumType });
            return (T)ctor.Invoke(new object[] { pObject });
0 голосов
/ 31 августа 2010

С ответом Ганса я смог заставить его работать, и если кому-то интересно, вот исправленная версия:

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

Но дополнительные проверки / код для обнуляемого перечисления снижают производительность для всех других типов. Если раньше метод расширения был ~ 2-3 медленнее, то сейчас ~ 10-15 раз. Делая это 1000000 (миллион) раз, используя код выше:

Распаковка int: 4 мс
Распаковка int с использованием метода расширения: 59 мс (раньше, без учета нуля enum: 12 мс)
Распаковка в nullable enum: 5ms
Распаковка в nullable enum методом расширения: 3382ms

Итак, при рассмотрении этих чисел эти методы не должны быть первыми, когда производительность критична - по крайней мере, при использовании ее для значений nullable.

...