Writing object to object mapper: first implementations
I wrote some object to object mapping code and introduced it in some of my previous postings about performance. As I received 22x performance raise when trying out different mapping methods and it is now time to make my code nice. In this posting I will show you how I organized my code to classes. Yes, you can use my classes in your work if you like. :)
- Using LINQ and reflection to find matching properties of objects
- Performance: Using dynamic code to copy property values of two objects
- Performance: Using LCG to copy property values of two objects
- Writing object to object mapper: first implementations
- Writing object to object mapper: moving to generics
- Writing object to object mapper: my mapper vs AutoMapper
- My object to object mapper source released
- Using Roslyn to build object to object mapper
In this posting I will create base class for my O/O-mappers. Also I will provide three implementations of O/O-mapper. There will be reflection based, dynamic code based and IL-instructions based implementations.
NB! Although implementations given here work pretty nice they are very general and therefore not maximally optimal. I will provide faster implementations in my next posting about object to object mapper.
Creating mapper base
I wrote simple base class that provides base type and some common functionality to mapper implementations. Common functionality comes in two methods: GetMapKey() and GetMatchingProperties(). First one of them creates unique string key for two mapped types. Second one queries source and target types and decides what properties are matching. These methods are protected because there is no reason for external code to call them. If you write your own implementations you can override these methods is you like.
public class PropertyMap
{
public PropertyInfo SourceProperty { get; set; }
public PropertyInfo TargetProperty { get; set; }
}
public abstract class ObjectCopyBase
{
public abstract void MapTypes(Type source, Type target);
public abstract void Copy(object source, object target);
protected virtual IList<PropertyMap> GetMatchingProperties
(Type sourceType, Type targetType)
{
var sourceProperties = sourceType.GetProperties();
var targetProperties = targetType.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(Type sourceType, Type targetType)
{
var keyName = "Copy_";
keyName += sourceType.FullName.Replace(".", "_");
keyName += "_";
keyName += targetType.FullName.Replace(".", "_");
return keyName;
}
}
Properties query given here is primitive one. My own query is far more complex and has many more conditions. I don’t want to drive your attention away from main topic and that’s why I am using simple implementation of query here. Querying result is property map for two given types. Every property of source type that fits is matched to property of target type.
ObjectCopyReflection – reflection based implementation
My first implementation was based on reflection. It made heavy use of it and was not as optimal as I wanted. Of course, it has also bright side – it is simplest one to read and understand. Just open Google if you are not familiar with reflection and it takes you couple of minutes to understand how this implementation works.
public class ObjectCopyReflection : ObjectCopyBase
{
private readonly Dictionary<string, PropertyMap[]> _maps =
new Dictionary<string, PropertyMap[]>();
public override void MapTypes(Type source, Type target)
{
if (source == null || target == null)
return;
var key = GetMapKey(source, target);
if (_maps.ContainsKey(key))
return;
var props = GetMatchingProperties(source, target);
_maps.Add(key, props.ToArray());
}
public override void Copy(object source, object target)
{
if (source == null || target == null)
return;
var sourceType = source.GetType();
var targetType = target.GetType();
var key = GetMapKey(sourceType, targetType);
if (!_maps.ContainsKey(key))
MapTypes(sourceType, targetType);
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);
}
}
}
If you look at Copy() method you can see that this method is pretty safe – it checks if mapping is for types already there and if it is not it creates it. This method was not perfect one because in my test environment it took about 0.0238 ms to copy matching properties from one object to another. Using this class I got the following result: 0,0240 ms.
ObjectCopyDynamicCode – dynamically compiled C#
Next implementation was remarkably faster. I used dynamically generated C# code and it gave me 0,0055 ms as result. Implementation for dynamically compiled C# mapper is here.
public class ObjectCopyDynamicCode : ObjectCopyBase
{
private readonly Dictionary<string, Type> _comp =
new Dictionary<string, Type>();
public override void MapTypes(Type source, Type target)
{
if (source == null || target == null)
return;
var key = GetMapKey(source, target);
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(source, target);
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}");
// Write out method body
Debug.WriteLine(builder.ToString());
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());
// Compiler output
foreach (var line in results.Output)
Debug.WriteLine(line);
var copierType = results.CompiledAssembly.GetType("Copy." + key);
_comp.Add(key, copierType);
}
public override void Copy(object source, object target)
{
if (source == null || target == null)
return;
var sourceType = source.GetType();
var targetType = target.GetType();
var key = GetMapKey(sourceType, targetType);
if (!_comp.ContainsKey(key))
MapTypes(sourceType, targetType);
var flags = BindingFlags.Public | BindingFlags.Static |
BindingFlags.InvokeMethod;
var args = new[] { source, target };
_comp[key].InvokeMember("CopyProps", flags, null, null, args);
}
}
This implementation is way better than previous one. It takes 0,0058 ms per copying operation. The code is not so simple to read and understand but we got better performance.
ObjectCopyLcg – dynamically generated IL-code
Our last implementation is based on LCG (Lightweight Code Generation) and this code has best performance as you saw from the chart given in LCG posting. Here is the code of LCG implementation of object copy class.
public class ObjectCopyLcg : ObjectCopyBase
{
private readonly Dictionary<string, DynamicMethod> _del =
new Dictionary<string, DynamicMethod>();
public override void MapTypes(Type source, Type target)
{
if (source == null || target == null)
return;
var key = GetMapKey(source, target);
if (_del.ContainsKey(key))
return;
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(source, target);
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);
_del.Add(key, dm);
}
public override void Copy(object source, object target)
{
var sourceType = source.GetType();
var targetType = target.GetType();
var key = GetMapKey(sourceType, targetType);
var del = _del[key];
var args = new[] { source, target };
del.Invoke(null, args);
}
}
Now let’s see how well this implementation performs. The result is a little bit unexpected:0,0084 ms. Okay, there is a little gotcha but let it be your homework if you have nothing better to do.
Test method
Here is my simple test method that measures how much time each implementation takes with given to objects. After measuring it writes results to console window. You can use this code to make testing easier.
public static void TestMappers(object source, object target)
{
var mappers = new ObjectCopyBase[]
{
new ObjectCopyReflection(),
new ObjectCopyDynamicCode(),
new ObjectCopyLcg()
};
var sourceType = source.GetType();
var targetType = target.GetType();
foreach (var mapper in mappers)
{
mapper.MapTypes(sourceType, targetType);
var stopper = new Stopwatch();
stopper.Start();
for (var i = 0; i < 100000; i++)
{
mapper.Copy(source, target);
}
stopper.Stop();
var time = stopper.ElapsedMilliseconds / (double)100000;
Console.WriteLine(mapper.GetType().Name + ": " + time);
}
}
Summary
In this posting I showed you how I organized my previously written dirty code to classes and achieved better readability and maintainability of code. Abstract base class serves also very well – I am able to cast different mapper instances to base type and therefore it is easier for me to measure their performance and to use them with IoC containers like Unity.
In the next posting about object to object mapper I will show you how to modify these classes to gain better performance.
How this can be use against converting datareader to property info class.
DataReader is totally different thing and needs different handling. When I have packed this mapper to something that people can download, try and use I will start with mapper that works with ADO.NET objects.
Pingback:Writing object to object mapper: moving to generics | Gunnar Peipman - Programming Blog