Невозможно создать TypeConverter для общего типа

Я хотел бы создать TypeConverter для общего класса, например:

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

Проблема заключается в том, что у вас не может быть общего в этой строке, она не разрешена:

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

Мой следующий подход состоял в том, чтобы попытаться определить один, не общий конвертер, который мог бы обрабатывать любые экземпляры Wrapper<T>. Смешение как рефлексии, так и дженериков меня озадачило, как реализовать оба методы ConvertTo и ConvertFrom.

Так, например, мой ConvertTo выглядит так:

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

В ConvertFrom У меня есть самая большая проблема, потому что у меня нет способа узнать, какой класс Wrapper будет преобразовывать входящие строки в.

Я создал несколько типов custome и TypeConverters для использования с каркасом веб-API ASP.NET 4, и именно там мне нужно это и для использования.

Еще одна вещь, которую я пробовал, заключалась в том, чтобы назначить мою родовую версию конвертера во время выполнения, как показано здесь, но структура WebAPI не уважала его (что означает, что преобразователь никогда не создавался).

Одна последняя заметка: я использую .NET 4.0 и VS 2010.

Ответ 1

Я решил это, создав один конвертер, который мог бы hanlde всех типов, извлеченных из моего общего класса. Большая проблема знать общий arg T в ConvertFrom была решена путем захвата информации в конструкторе, как показано ниже.

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

Мне потребовалось много времени, чтобы обнаружить, что инфраструктура .NET рефлексивно вызывает эту перегрузку конструктора, если она определена. Он не был частью документированного класса TypeConverter.

Надеюсь, это все поможет следующему парню.

Ответ 2

Хотя ответ @tcarvin очень интересен - он работает для меня в .NET Framework 4.6.1, и из того, что я вижу в коде, он также должен работать на .NET Core, есть альтернативное решение с использованием TypeDescriptionProviderAttribute которое не зависит от этого. подробности реализации, которые он описывает (конструктор, принимающий параметр типа Type).

Наличие:

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

вам просто нужно применить TypeDescriptionProviderAttribute к целевому классу, например:

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

а потом

TypeDescriptor.GetConverter(typeof(Foo)) будет работать как положено.