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.


3 thoughts on “Writing object to object mapper: moving to generics

Leave a Reply

Your email address will not be published. Required fields are marked *