InvalidCastException с пользовательским универсальным TypeConverter для использования в среде MCV, необходимо исправить - PullRequest
1 голос
/ 31 января 2012

Мне нужен TypeConverter, который в идеале преобразует String в класс перечислимых типов (CountryIso) без необходимости писать конвертер для каждого перечисляемого типа.

Пока мне удалось заставить работать следующее:

CountryIso cI = (CountryIso) "1";

Я просто не могу заставить его работать с генериками! Следующий пример не работает, но почему?

TypeDescriptor.AddProvider(new ExplicitCastDescriptionProvider<CountryIso>(), typeof(CountryIso));
var descriptor = TypeDescriptor.GetConverter(typeof(CountryIso));
var result = descriptor.ConvertFrom("1");

В настоящее время у меня есть общая реализация TypeConverter:

public class ExplicitCastConverter<T>: TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        // Always true: the type determines if a cast is available or not
        return true;
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        String dummy = (String) value;
        //CountryIso tst = (CountryIso) value; // Allowed, no problem casting
        //CountryIso tst = (CountryIso) dummy; // Allowed, no problem casting
        //var dum_001 = (T) ((String) value); // Does not compile
        //var dumdum = (T) value; // Invalid case exception
        //var hoot = (T) Convert.ChangeType(value, typeof (T)); // Invalid cast exception
        return null;
    }
}

Поставщик выглядит следующим образом:

//thanks: http://groups.google.com/group/wpf-disciples/browse_thread/thread/9f7bb40b7413fcd
public class ExplicitCastDescriptionProvider<T> : TypeDescriptionProvider //where T:TypeSafeEnum
{
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        return new ImplicitCastDescription<T>();
    }
}

public class ImplicitCastDescription<T>: CustomTypeDescriptor //where T:TypeSafeEnum
{
    public override TypeConverter GetConverter()
    {
        return new ExplicitCastConverter<T>();
    }
}

У меня есть реализация безопасного перечисления типа CountryIso (спасибо StackOverflow!):

public sealed class CountryIso: TypeSafeEnum
{
    private static readonly Dictionary<int, CountryIso> InstanceDict = new Dictionary<int, CountryIso>();

    public static readonly CountryIso NL = new CountryIso(1, "NL", "Netherlands");
    public static readonly CountryIso BE = new CountryIso(2, "BE", "Belgium");

    private CountryIso(int value, String name, String description): base(value,name,description)
    {
        InstanceDict.Add(value, this);
    }

    public static Dictionary<int, CountryIso> Instances
    {
        get { return new Dictionary<int, CountryIso>(InstanceDict); }
    }

    public static explicit operator CountryIso(String i)
    {
        int index;
        return Int32.TryParse(i,out index) ? InstanceDict[index] : null;
    }
}

Что наследуется от TypeSafeEnum:

public class TypeSafeEnum
{
    protected TypeSafeEnum(int value, String name, String description)
    {
        Name = name;
        Value = value;
        Description = description;
    }

    public int Value{ get; private set; }
    public String Name { get; private set; }
    public String Description { get; private set; }
}

1 Ответ

0 голосов
/ 02 февраля 2012

Один вариант: использовать отражение

Проблема заключается в статической природе членов CountryIso и ( в основном ) операторов приведения. Это предотвращает любое определение схемы, позволяя универсальному преобразователю типов знать, что он может привести безопасное перечисление типа CountryIso. Кроме того, вы не можете бросить «вниз»: TypeSafeEnum никогда не может стать CountryIso. Что логично, но не помогает.

[Использование отражения]

  1. Введен общий интерфейс, определяющий метод приведения:

    public interface ICast<out T>
    {
        T Cast(String obj);
    }
    
  2. Применить интерфейс к CountryIso

    public sealed class CountryIso: TypeSafeEnum , ICast<CountryIso>
    
  3. Добавление интерфейса в качестве контраста в класс преобразователя

    public class ExplicitCastConverter<T>: TypeConverter where T: ICast<T>
    
  4. Добавить (нестатический) метод приведения в мой CountryIso:

    public new CountryIso Cast(String obj)
    {
        int index;
        return Int32.TryParse(obj, out index) ? InstanceDict[index] : null;
    }
    
  5. Добавлен статический член по умолчанию в мой type-safe-enum:

    private static readonly CountryIso DefaultTypeSafeEnum = new CountryIso(
        -1,
        null,
        null
    );
    
  6. Реализация ConvertFrom (..) в классе Converter:

    T defaultMember = (T)typeof(T).GetField(
        "DefaultTypeSafeEnum",
        BindingFlags.NonPublic | BindingFlags.Static
    ).GetValue(null);
    
    return defaultMember.Cast((String) value);
    

[Введите безопасный enum security]

Это все еще возможно создавать и вставлять новые экземпляры CountryIso с помощью отражения (особенно при использовании InstanceDict для легкого доступа к экземпляру!) Некоторые примеры кода:

ConstructorInfo ci = typeof (T).GetConstructor(
    BindingFlags.NonPublic|BindingFlags.Instance,
    null,
    CallingConventions.Standard,
    new [] {typeof (int), typeof (String), typeof (String)},
    new ParameterModifier[0]
);

CountryIso countryIso = (CountryIso) ci.Invoke(new object[]{30, "ZB", "Zanzibar"});

Теперь я считаю использование частного члена InstanceDict дырой в безопасности (не большая, поскольку я не программирую API для внешнего мира, но все же ...)

...