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 4th November 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;
}

Posted in .net | No Comments »

Quick Debug a Value (Chaining)

Posted by Ricibald on 24th January 2014

This code fails on method Pay:

Customer customer = repository.GetCustomer(id);
customer.GetOpenOrders().GetLatestOrderItem().Pay();

You have to debug the result value of the method GetOpenOrders. To accomplish this you have to add one row to split the statement:

Customer customer = repository.GetCustomer(id);
IEnumerable<Order> openOrders = customer.GetOpenOrders();   // BREAKPOINT HERE
openOrders.GetLatestOrderItem().Pay();

Having one more row we can now add a breakpoint on row 2. This approach requires to rewrite the statement.
Just adding this extension method we can now debug every value:

[System.Diagnostics.DebuggerStepThrough]
public static T Break<T>(this T t) {
    Debugger.Break();
    return t;
}

Just use in this way:

Customer customer = repository.GetCustomer(id);
customer.GetOpenOrders().Break().GetLatestOrderItem().Pay();

No new rows, just chaining :-)!!

Posted in .net | No Comments »

Automapper: Ignore All Not Mapped Properties

Posted by Ricibald on 19th July 2013

Normally in automapper you’ll write something like this:

Mapper.CreateMap<MoveEntity, MoveEntityDto>()
       .ForMember(dest => dest.PrimaryOriginTransferee, opt => opt.Ignore())
       .ForMember(dest => dest.PrimaryDestinationTransferee, opt => opt.Ignore())
       .ForMember(dest => dest.Customer, opt => opt.Ignore())
       .ForMember(dest => dest.DestinationAddress, opt => opt.Ignore())
       .ForMember(dest => dest.OriginAddress, opt => opt.Ignore())
       .ForMember(dest => dest.Order, opt => opt.Ignore())
       .ForMember(dest => dest.Shipment, opt => opt.Ignore())
       .ForMember(dest => dest.SourceSystemName, opt => opt.Ignore());

To ignore all not-mapped properties just use this extension method:

Mapper.CreateMap<MoveEntity, MoveEntityDto>()
       .IgnoreAllNonExisting();

Here the source code:

    public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
    {
        var sourceType = typeof(TSource);
        var destinationType = typeof(TDestination);
        var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType.Equals(sourceType) && x.DestinationType.Equals(destinationType));
        foreach (var property in existingMaps.GetUnmappedPropertyNames())
        {
            expression.ForMember(property, opt => opt.Ignore());
        }
        return expression;
    }

Posted in .net | 2 Comments »