Friday, June 15, 2007

Generic Type Conversion in C#

I have now several times found myself in the peculiar situation of having two classes, that mostly have the same properties and fields, but doesn't implement the same interface or inherit from the same ancestor, which makes it rather tedious to convert between them.
The problem is typically seen when dynamically loading 3rd party libraries and trying to get them to interact with other 3rd party libraries, or when doing some advanced kinds of communication (like WCF) with complex types.
True, in some cases the problems can be avoided totally by considering your data, assembly and communication structure - but still there's those hopeless cases where you find yourself writing yet another "Create an object of this type based on that type"-code piece. Not a difficult task, but boring.
So, as always I've tried to come up with a stupid solution to a stupid problem: A Generic Type Converter. It uses Generics to "convert" type A to type B. By Converting it simply matches up the public properties and fields and copies the ones that match and that it's allowed to.
Sure, it might be slow (and some might even find it ugly) - but for tasks where the development time is more critical than a few miliseconds of execution time (like for POC's) it might be a nice thing to have in your toolbox.

Keep in mind that this is just a draft version 0.0.0.01.

P.S. Sorry about the formatting, but I'm having a small war with my blogspot and some CSS :-)




using System.Reflection;

public static class GenericTypeConverter
{

public static DestType ConvertType<SrcType, DestType>(SrcType Source) where DestType:class,new()
{
return ConvertType<SrcType,DestType>(Source, null, null);
}

public static DestType ConvertType<SrcType, DestType>(SrcType Source, Dictionary<string, string> SrcDestMapping) where DestType : class,new()
{
return ConvertType<SrcType,DestType>(Source, SrcDestMapping,null);
}

private static string GetMappedName(Dictionary<string, string> Map, string OrigName)
{
if((Map!=null)&&(Map.ContainsKey(OrigName))) return Map[OrigName];
return OrigName;
}

/// <summary>
/// Uses reflection to convert an object to a destination type, e.g. transfers all the properties and members they have in common
/// </summary>
/// <typeparam name="SrcType">Source Type</typeparam>
/// <typeparam name="DestType">Destination Type</typeparam>
/// <param name="Source">Object to convert</param>
/// <param name="SrcDestMap">Mapping between source and destination property names. Null if no mapping exist.</param>
/// <param name="Dest">Destination object or null if it should be created</param>
/// <returns>An object where as many properties and fields as possible have been transferred from Source.</returns>
private static DestType ConvertType<SrcType, DestType>(SrcType Source, Dictionary<string, string> SrcDestMap, DestType Dest) where DestType : class
{
//Create object if it doesn't exist.
DestType dstVar = Dest;
if (dstVar == null) dstVar = Activator.CreateInstance<DestType>();

//Loop through Source' public properties
Type srcTp = typeof(SrcType);
PropertyInfo[] props=srcTp.GetProperties(System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Static
| System.Reflection.BindingFlags.GetProperty);
foreach (PropertyInfo p in props)
{
//Check if destination type has a settable property of the same type
PropertyInfo pDest = typeof(DestType).GetProperty(GetMappedName(SrcDestMap,p.Name), p.PropertyType);
if ((pDest != null) && (pDest.CanWrite)) pDest.SetValue(dstVar, p.GetValue(Source, null), null);
}

//Loop through Source' public fields
FieldInfo[] mems=srcTp.GetFields();
foreach (FieldInfo fi in mems)
{
FieldInfo mDest = typeof(DestType).GetField(GetMappedName(SrcDestMap,fi.Name));
if ((mDest != null) && (fi.FieldType == mDest.FieldType))
{
mDest.SetValue(dstVar, fi.GetValue(Source));
}
}

return dstVar;
}
}

6 comments:

Elie said...

Vey usefull piece of code, Thanks!

Could I get some help with a somewhat related issue:

I have a class A which derives from class B

public class A : B
{...}

I am using a strongly typed dictionary:
Dictionary < int, A > a;

and I am looking for an easy way to use this dictionary using the base type:
Dictionary < int, B > b;

i.e. do something like:

b = a as Dictionary < int, B > ;

Of course (?!) It won't compile...

Any idea ?

Thanks !

Allan Thræn said...

Hi Elie

I'm glad you like the code.

I think you need to convert each of the values in your dictionary in order to convert a Dictionary<int,A> to a Dictionary<int,B>.
An easier approach would be to simple use a Dictionary<int,B> - you could still easily add A elements to that. Alternatively you can use the values of A type implicitly as B's (B myvalue=a[0];) or explicitly (B myvalue=(B)a[0];)
I hope this helps you out :-)

Charlie said...

Elie, what you're asking about is contravariance and covariance - being able to determine inheritance relationships based on generic type params. The good news is it's coming in future .NET versions. The bad news of course: it's not yet here.

Steve Strong said...
This comment has been removed by a blog administrator.
Marcel Steinbach said...

Hm... at first it looks fine. But when i have complex Types (parent and child data for example) the Conversion must be able to convert collections also. How must the solution be changed ?

Anonymous said...

Marcel and all,

There are Object-to-Object mapping frameworks that take this to the next level:

http://code.google.com/p/otis-lib/

http://code.google.com/p/automapperhome/