Mapping models (Little reflection exercise in C#)

Posted on Sep 21, 2015 | Categories: coding


Once upon a time in a little office there was a developer writing code to map two different models to each other. This developer was me, and I didn’t want to have to write mapping code until I retire or until I die. This actually is the the reason which motivated me to write this code to map models to each other.

I will definitely get the question, why don’t you use Automapper? When writing the software I couldn’t find a way to make Automapper work on the fly. With this I mean the possibility of mapping two models to each other without first defining the mapping in some configuration class. I’m lazy, I love dynamic stuff, and I wanted something simple to use wherever I wanted it to use.

Back when I wrote this awful piece of code (already over ~~half~~ a year ago at the time of writing) it took me two days due to some ‘complex’ reflection operations and some edge cases which had to be handled.  Lets walk through the code which I wrote back then and some of the basic requirements:

  • Models must be mapped to each other based on their property names

  • `IEnumerable<T>` types must be mapped to a comparable type

  • Complex types also must be mapped to a comparable type (mostly `ICollection<T>`)

With the basic rules above in mind I started writing a little bit code back in the days. While working on this code I found discovered some additional gotchas which may or may not be obvious at your first look at the requirements above.

  • Because we also want to map child properties (regardless of their type, whether it’s a `string` or `ICollection<T>`) we have to incorporate this into the design of the software. (Don’t worry, I put most of the stuff in a single file anyway.)

  • We’d love to call our mapper method wherever we want. This may be a good place to actually use an extension method.

In the code which comes with this post you can see we are just copying the properties from one object to another. There are a few edge cases handled (with dirty old try catch statements) for handling reference types and collections.

Because the code is pretty well documented (I guess) and I got the whole explanation about the goal of this code above here I think it’s pretty straightforward to use it now, or at least, to use it as reference for your own project.

Also, sorry folks, no tests. This code worked for me as is, and I thought it’d be nice to share this old gem with you. (See the raw snippet here, see snippet with proper highlighting here)

public static class Mapper<T> where T : class
{
    public static T Map(object obj)
    {
        dynamic instance = Activator.CreateInstance<T>();

        Map(ref instance, obj);

        return (T)instance;
    }

    private static void Map(ref dynamic instance, object obj)
    {
        var destination = instance.GetType();
        var source = obj.GetType();

        foreach (PropertyInfo property in destination.GetProperties())
        {
            PropertyInfo sourceProp = source.GetProperty(property.Name);
            if (sourceProp != null)
            {
                // The moment the object cannot be converted, which most of the times means a different data time is used in one of the objects, try to convert child items.

                try
                {
                    property.SetValue(instance, source.GetProperty(property.Name).GetValue(obj));
                }
                catch
                {
                    try
                    {
                        var sp = sourceProp.GetValue(obj);
                        Type t = Type.GetType(property.PropertyType.AssemblyQualifiedName);

                        // If the source is a list, do some alternative conversion
                        var sourceList = sp as IEnumerable;
                        if (sourceList != null)
                        {
                            // If the source has the type of an ICollection we can almost be sure the destination also has the type of an ICollection.
                            var destinationList =
                                Activator.CreateInstance(
                                    typeof(Collection<>).MakeGenericType(
                                        t.GenericTypeArguments[0]));

                            foreach (var sourceItem in sourceList)
                            {
                                destinationList.GetType().GetMethod("Add").Invoke(destinationList, new[]
                                {
                                    typeof (Mapper<>).MakeGenericType(t.GenericTypeArguments[0])
                                        .GetMethod("Map")
                                        .Invoke(null, new[] {sourceItem})
                                });
                            }

                            property.SetValue(instance, destinationList);
                        }
                        else
                        {
                            // sp isn't an ICollection
                            object notherinstance = Activator.CreateInstance(t);

                            Map(ref notherinstance, sp);

                            property.SetValue(instance, notherinstance); // Do some conversion here
                        }
                    }
                    catch
                    {
                        // When some error is catched it maybe means the property value in the source object is null.

                        // Well, there's so much which could've gone wrong when you're at this point...
                    }
                }
            }
        }
    }
}