在MCV环境中使用自定义通用TypeConverter的InvalidCastException,需要修复

时间:2012-01-31 14:18:58

标签: c# generics casting typeconverter

我需要一个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 :(得分:0)

一个选项:使用反射

问题在于CountryIso成员的静态特性以及(主要是)转换运算符。这可以防止任何蓝图定义使通用的类型转换器知道它可以转换CountryIso类型的安全枚举。此外,您无法强制转换'down':TypeSafeEnum永远不会成为CountryIso。这是合乎逻辑的,但没有帮助。

[使用反射]

  1. 引入了定义转换方法的通用接口:

    public interface ICast<out T>
    {
        T Cast(String obj);
    }
    
  2. 将接口应用于CountryIso

    public sealed class CountryIso: TypeSafeEnum , ICast<CountryIso>
    
  3. 将接口作为contstraint添加到转换器类

    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. 在Converter类中实现ConvertFrom(..):

    T defaultMember = (T)typeof(T).GetField(
        "DefaultTypeSafeEnum",
        BindingFlags.NonPublic | BindingFlags.Static
    ).GetValue(null);
    
    return defaultMember.Cast((String) value);
    
  7. [类型安全枚举安全]

    仍然可以创建&amp;通过反射插入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,但仍然......)