X

Writing object to object mapper: moving to generics

In my previous posting about object to object mapping Writing object to object mapper: first implementations I wrote first and simple implementations of mapper. These implementations based heavily on reflection. In this posting I will boost up mapper performance by using generics.

In most parts the code here is same as before but instead of laying heavily on objects we make use of generics so we can maybe achieve better performance than before.

Base class and property map

Property map is still the same but object base is moved to generic methods.

public class PropertyMap
{
    public PropertyInfo SourceProperty { get; set; }
    public PropertyInfo TargetProperty { get; set; }
}
 
public abstract class ObjectCopyBase
{
 
    public abstract void MapTypes<T, TU>();
    public abstract void Copy<T, TU>(T source, TU target);
 
    protected virtual IList<PropertyMap>
        GetMatchingProperties<T, TU>()
    {
        var sourceProperties = typeof(T).GetProperties();
        var targetProperties = typeof(TU).GetProperties();
 
        var properties = (from s in sourceProperties
                          from t in targetProperties
                          where s.Name == t.Name &&
                                s.CanRead &&
                                t.CanWrite &&
                                s.PropertyType == t.PropertyType
                          select new PropertyMap
                          {
                              SourceProperty = s,
                              TargetProperty = t
                          }).ToList();
        return properties;
    }
 
    protected virtual string GetMapKey<T, TU>()
    {
        var className = "Copy_";
        className += typeof(T).FullName.Replace(".", "_");
        className += "_";
        className += typeof(TU).FullName.Replace(".", "_");
 
        return className;
    }
}

These changes to non-abstract methods are marginal and we don’t have to do these changes if we don’t want. They don’t affect performance as we see later.

ObjectCopyReflection

Here is the generic implementation of ObjectCopyReflection.

public class ObjectCopyReflection : ObjectCopyBase
{
    private readonly Dictionary<string, PropertyMap[]> _maps =
        new Dictionary<string, PropertyMap[]>();
 
    public override void MapTypes<T, TU>()
    {
        var source = typeof(T);
        var target = typeof(TU);
        var key = GetMapKey<T, TU>();
        if (_maps.ContainsKey(key))
            return;
 
        var props = GetMatchingProperties<T, TU>();
        _maps.Add(key, props.ToArray());
    }
 
    public override void Copy<T, TU>(T source, TU target)
    {
        var key = GetMapKey<T, TU>();
        if (!_maps.ContainsKey(key))
            MapTypes<T, TU>();
 
        var propMap = _maps[key];
 
        for (var i = 0; i < propMap.Length; i++)
        {
            var prop = propMap[i];
            var sourceValue = prop.SourceProperty.GetValue
                              (source, null);
            prop.TargetProperty.SetValue(target, sourceValue,
                                         null);
        }
    }
}

Running this implementation gives us 0,0242 ms as result. It is practically same as before and moving this implementation to generics had no impact on performance.

ObjectCopyDynamicCode

Now let’s take ObjectCopyDynamicCode implementation.

public class ObjectCopyDynamicCode : ObjectCopyBase
{
    private readonly Dictionary<string, Type> _comp =
        new Dictionary<string, Type>();
 
    public override void MapTypes<T, TU>()
    {
        var source = typeof(T);
        var target = typeof(TU);
 
        var key = GetMapKey<T, TU>();
        if (_comp.ContainsKey(key))
            return;
 
        var builder = new StringBuilder();
        builder.Append("namespace Copy {\r\n");
        builder.Append("    public class ");
        builder.Append(key);
        builder.Append(" {\r\n");
        builder.Append("        public static void CopyProps(");
        builder.Append(source.FullName);
        builder.Append(" source, ");
        builder.Append(target.FullName);
        builder.Append(" target) {\r\n");
 
        var map = GetMatchingProperties<T, TU>();
        foreach (var item in map)
        {
            builder.Append("            target.");
            builder.Append(item.TargetProperty.Name);
            builder.Append(" = ");
            builder.Append("source.");
            builder.Append(item.SourceProperty.Name);
            builder.Append(";\r\n");
        }
 
        builder.Append("        }\r\n   }\r\n}");
 
        var myCodeProvider = new CSharpCodeProvider();
        var myCodeCompiler = myCodeProvider.CreateCompiler();
        var myCompilerParameters = new CompilerParameters();
        myCompilerParameters.ReferencedAssemblies.Add(
            typeof(LinqReflectionPerf).Assembly.Location
        );
        myCompilerParameters.GenerateInMemory = true;
        var results = myCodeCompiler.CompileAssemblyFromSource
            (myCompilerParameters, builder.ToString());
 
        var copierType = results.CompiledAssembly.GetType
                         ("Copy." + key);
        _comp.Add(key, copierType);
    }
 
    public override void Copy<T, TU>(T source, TU target)
    {
        var key = GetMapKey<T, TU>();
        if (!_comp.ContainsKey(key))
            MapTypes<T, TU>();
 
        var flags = BindingFlags.Public | BindingFlags.Static |
                    BindingFlags.InvokeMethod;
        var args = new object[] { source, target };
        _comp[key].InvokeMember("CopyProps", flags, null, null,
                                 args);
    }
}

The result is 0,0059 ms and it is also same as before. So we don’t have any luck here too.

ObjectCopyLcg

Before all hope is gone let’s see what LCG (Lightweight Code Generation) implementation of mapper thinks about generics.

public class ObjectCopyLcg : ObjectCopyBase
{
    private delegate void CopyPublicPropertiesDelegate<T, TU>
        (T source, TU target);
 
    private readonly Dictionary<string, object> _del =
        new Dictionary<string, object>();
 
    public override void MapTypes<T, TU>()
    {
        var key = GetMapKey<T, TU>();
        if (_del.ContainsKey(key))
            return;
 
        var source = typeof(T);
        var target = typeof(TU);
 
        var args = new[] { source, target };
        var mod = typeof(Program).Module;
 
        var dm = new DynamicMethod(key, null, args, mod);
        var il = dm.GetILGenerator();
        var maps = GetMatchingProperties<T, TU>();
 
        foreach (var map in maps)
        {
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_0);
            il.EmitCall(OpCodes.Callvirt,
                        map.SourceProperty.GetGetMethod(), null);
            il.EmitCall(OpCodes.Callvirt,
                        map.TargetProperty.GetSetMethod(), null);
        }
        il.Emit(OpCodes.Ret);
        var del = dm.CreateDelegate(
                  typeof(CopyPublicPropertiesDelegate<T, TU>));
        _del.Add(key, del);
    }
 
    public override void Copy<T, TU>(T source, TU target)
    {
        var key = GetMapKey<T, TU>();
        var del = (CopyPublicPropertiesDelegate<T, TU>)_del[key];
        del.Invoke(source, target);
    }
}

The result is 0,0019 ms and it is ~4.5x better than before. Although LCG implementation was not the fastest one before it is now the best implementation we have.

Results

Let’s put previous and current results to table and let’s compare them.

Implementation Non-generic Generic Difference
ObjectCopyReflection 0,0240 ms 0,0242 ms ~1,0 x
ObjectCopyDynamicCode 0,0058 ms 0,0059 ms ~1,0 x
ObjectCopyLcg 0,0084 ms 0,0019 ms ~4,5 x

As we can see then first two implementations gave different results but differences were very-very small. We can practically say that there were no changes in performance. ObjectCopyLcg gave us ~4.5x times better result when we moved to generics.

Conclusion

This posting is good example about how generics are not silver bullets that fix performance problems automagically because they know the types. In our situation we achieved better results only in one implementation while other implementations stayed practically the same. In my next object to object mapper posting I will compare my results with AutoMapper.

Liked this post? Empower your friends by sharing it!
Categories: .NET C#

View Comments (3)

  • Just for completeness:

    public class ObjectCopyExpression : ObjectCopyBase
    {
    private readonly Dictionary<Tuple, List> _props = new Dictionary<Tuple, List>();

    public override void MapTypes()
    {
    Type sourceType = typeof(TSource);
    Type targetType = typeof(TTarget);
    Tuple key = new Tuple(sourceType, targetType);

    if (this._props.ContainsKey(key) == true)
    {
    return;
    }

    IEnumerable props = this.GetMatchingProperties();

    this._props [ key ] = new List();

    foreach (PropertyMap prop in props)
    {
    String propName = prop.SourceProperty.Name;

    ParameterExpression targetExpression = Expression.Parameter(targetType, "target");

    ParameterExpression sourceExpression = Expression.Parameter(sourceType, "source");

    PropertyInfo getter = prop.SourceProperty;

    PropertyInfo setter = prop.TargetProperty;

    MemberExpression sourcePropertyAccess = Expression.MakeMemberAccess(sourceExpression, getter);

    MemberExpression targetPropertyAccess = Expression.MakeMemberAccess(targetExpression, setter);

    BinaryExpression call = Expression.Assign(targetPropertyAccess, sourcePropertyAccess);

    LambdaExpression lambda = Expression.Lambda(call, sourceExpression, targetExpression);

    Delegate del = lambda.Compile();

    this._props [ key ].Add(del);
    }
    }

    public override void Copy(TSource source, TTarget target)
    {
    Type sourceType = typeof(TSource);
    Type targetType = typeof(TTarget);
    Tuple key = new Tuple(sourceType, targetType);

    if (this._props.ContainsKey(key) == false)
    {
    this.MapTypes();
    }

    foreach (Delegate del in this._props [ key ])
    {
    Object result = del.DynamicInvoke(source, target);
    }
    }
    }

    However, in my computer (.NET 4.0) ObjectCopyReflection is the fastest.

  • Thanks for feedback, Ricardo! :)

    This is strange that ObjectCopyReflection performs best in your machine. It has a lot more overhead than LCG - I mean .NET framework has to go through more instructions to run. LCG stuff is small and compact - there is not additional code used besides the one that makes actual work for us.

Related Post