Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

.NET: convertire due tipi generici

Posted by Ricibald on November 4th, 2014

Come convertire in modo generico un tipo in un altro tipo?

Immaginiamo di dover convertire un tipo generico MyCustomType in un altro tipo (es. string). Ci sono diversi modi: dal semplice ToString() a un cast verso string. Finche si converte da MyCustomType verso string non è un problema, ma quando si procede al contrario questo non è possibile poiché string è fuori dal nostro controllo.

Per queste casistiche esiste da .NET 2.0 l’attributo TypeConverter da dichiarare all’interno di MyCustomType:

[TypeConverter(typeof(MyCustomTypeConverter))]
 public class MyCustomType {
    private string Data { get; set; }
    public static MyCustomType Parse(string data) {
        // convert code from string to MyCustomType here...
        return new MyCustomType { Data = data };
    }
    public override ToString() {
        // convert code from MyCustomType to string here...
        return this.Data;
    }
 }

public class MyCustomTypeConverter : TypeConverter {
    public override bool CanConvertTo (ITypeDescriptorContext context, Type destinationType)
    {
        return destinationType == typeof(string) || base.CanConvertTo (context, destinationType);
    }

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
            return MyCustomType.Parse ((string)value);  // YOUR CONVERT HERE

        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo (ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        var myCustomTypeToConvert = (MyCustomType)value;
        if (destinationType == typeof(string))
            return myCustomTypeToConvert.ToString ();   // YOUR CONVERT HERE

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

Una volta costruito il converter si invoca nel seguente modo per convertire da/a un certo tipo:

MyCustomType myCustomType = new MyCustomType { Data = "MyData" };
TypeConverter myCustomTypeConverter = TypeDescriptor.GetConverter (typeof(MyCustomType));

string myCustomTypeAsText = (string) myCustomTypeConverter.ConvertTo(myCustomType, typeof(string));
MyCustomType myCustomTypeFromText = (MyCustomType) myCustomTypeConverter.ConvertFrom("MyData2");

Questo risolve il problema finquando sappiamo quale dei due tipi implementa il TypeConverter per quella particolare conversione, cosa che potremmo non sapere in una gestione completamente generica.

Per questo motivo ho implementato il seguente extension method che consente di convertire due tipi generici andando a inferire in automatico quale TypeConverter utilizzare:

/// <summary>
/// Converte l'istanza. Usa il TypeDescriptor di T
/// </summary>
public static TDestination ConvertSafe<TDestination>(this object value) {
    if (value == null)
        return default(TDestination);

    // se sono gia lo stesso tipo non fare nulla
    if (typeof(TDestination) == value.GetType ())
        return (TDestination)value;

    // prova a convertire usando il converter di T
    var targetConverter = TypeDescriptor.GetConverter (typeof(TDestination));
    if(targetConverter != null && targetConverter.CanConvertFrom(value.GetType())) 
        return (TDestination)targetConverter.ConvertFrom (value);

    // prova a convertire usando il converter di value
    var sourceConverter = TypeDescriptor.GetConverter (value);
    if(sourceConverter != null && sourceConverter.CanConvertTo(typeof(TDestination))) 
        return (TDestination)sourceConverter.ConvertTo (value, typeof(TDestination));

    throw new InvalidCastException (String.Format("Unable to convert {0} to {1}", value.GetType(), typeof(TDestination)));
}

Esempio di utilizzo:

MyCustomType myCustomType = "MyData".ConvertSafe<MyCustomType>();
string myCustomTypeAsText = myCustomType.ConvertSafe<string>();

Che abilita scenari come il seguente che converte un tipo string (in teoria non convertibile) in un generico tipo MetaData<T> sfruttando internamente il TypeConverter del tipo T determinato a tempo di compilazione:

public MetaData<T> GetMetaInfo<T>(string value) {
    var result = new MetaData<T>(value.ConvertSafe<T>());
    return result;
}

Leave a Reply

You must be logged in to post a comment.