Cannot create a TypeConverter for a generic type

馋奶兔 提交于 2020-01-12 06:55:31

问题


I'd like to create a TypeConverter for a generic class, like this:

[TypeConverter(typeof(WrapperConverter<T>))]
public class Wrapper<T> 
{

   public T Value 
   {
      // get & set 
   }

   // other methods

}


public class WrapperConverter<T> : TypeConverter<T>
{

   // only support To and From strings
   public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
   {
      if (sourceType == typeof(string))
      {
         return true;
      }
      return base.CanConvertFrom(context, sourceType);
   }

   public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
   {
      if (destinationType == typeof(string))
      {
         return true;
      }
      return base.CanConvertTo(context, destinationType);
   }

   public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
   {
      if (value is string)           
      {
         TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
         T inner = converter.ConvertTo(value, destinationType);
         return new Wrapper<T>(inner);
      }
      return base.ConvertFrom(context, culture, value);
   }

   public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
   {
      if (destinationType == typeof(System.String))
      {
         Wrapper<T> wrapper = value as Wrapper<T>();
         TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
         return converter.ConvertTo(wrapper.Value, destinationType);
      }   
      return base.ConvertTo(context, culture, value, destinationType);
   }
}

The problem comes in that you cannot have a generic in this line, it is disallowed:

[TypeConverter(typeof(WrapperConverter<T>))]
public class Wrapper<T> 

My next approach was to try to define a single, non-generic Converter that could handle any Wrapper<T> instances. The mix of both reflection and generics has me stumped on how to implement both the ConvertTo and ConvertFrom methods.

So for example, my ConvertTo looks like this:

public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
   if (destinationType == typeof(System.String)           
       && value.GetType().IsGenericType)
   {

       // 1.  How do I enforce that value is a Wrapper<T> instance?

       Type innerType = value.GetType().GetGenericArguments()[0];

       TypeConverter converter = TypeDescriptor.GetConverter(innerType);

       // 2.  How do I get to the T Value property?  Introduce an interface that Wrapper<T> implements maybe?
       object innerValue = ??? 

       return converter.ConvertTo(innerValue, destinationType);


   }
   return base.ConvertTo(context, culture, value, destinationType);
}

In ConvertFrom I have the biggest problem because I have no way to know which Wrapper class to convert the incomming strings into.

I've created several custome types and TypeConverters for use with the ASP.NET 4 Web API framework, and that is where I need this to be used as well.

One other thing I tried was to assign my generic version of the Converter at runtime as seen here, but the WebAPI framework did not respect it (meaning, the converter was never created).

One last note, I'm using .NET 4.0 and VS 2010.


回答1:


I solved this by creating an single Converter that could hanlde all of the types derrived from my generic class. The big issue of knowing the generic arg T within the ConvertFrom was solved by capturing the information in the constructor as seen below.

public MyGenericConverter(Type type)
{
    if (type.IsGenericType 
        && type.GetGenericTypeDefinition() == typeof(MyGenericClass<>)
        && type.GetGenericArguments().Length == 1)
    {
        _genericInstanceType = type;
        _innerType = type.GetGenericArguments()[0];
        _innerTypeConverter = TypeDescriptor.GetConverter(_innerType);            
    }
    else
    {
        throw new ArgumentException("Incompatible type", "type");
    }
}

It took me ages to discover that the .NET infrastructure reflectively calls this constructor overload if it is defined. It was not part of the documented TypeConverter class.

Hope this all helps the next guy.




回答2:


Although @tcarvin's answer is very interesting - it works for me in .NET Framework 4.6.1 and from what I see in the code it should also work on .NET Core, there is an alternative solution using TypeDescriptionProviderAttribute that doesn't depend on that implementation detail he describes (constructor accepting parameter of type Type).

Having:

public class FooTypeConverter<T> : TypeConverter { ... }

public class FooTypeDescriptor : CustomTypeDescriptor
{
    private Type objectType;

    public FooTypeDescriptor(Type objectType)
    {
        this.objectType = objectType;
    }

    public override TypeConverter GetConverter()
    {
        var genericArg = objectType.GenericTypeArguments[0];
        var converterType = typeof(FooTypeConverter<>).MakeGenericType(genericArg);
        return (TypeConverter)Activator.CreateInstance(converterType);
    }
}

public class FooTypeDescriptionProvider : TypeDescriptionProvider
{
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        return new FooTypeDescriptor(objectType);
    }
}

you just need to apply TypeDescriptionProviderAttribute to the target class like:

[TypeDescriptionProvider(typeof(FooTypeDescriptionProvider))]
public class Foo<T> { }

and then

TypeDescriptor.GetConverter(typeof(Foo)) will work as intended.



来源:https://stackoverflow.com/questions/14823669/cannot-create-a-typeconverter-for-a-generic-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!