Performance: Using dynamic code to copy property values of two objects
Last year I wrote short posting about how to use LINQ to find matching properties of two objects. In this posting I will show you how to copy values from one object to another and how to boost up performance so you can use this strategy also on in servers under heavy load.
As this is lengthy posting I will give you right now some idea what we are going to do here:
- Stating the problem – when we need to copy property values
- Detecting matching properties of two objects using reflection and LINQ
- Copying matching properties (no optimizations)
- Copying matching properties (lightweight optimizations)
- Copying matching properties (heavily optimized)
- Comparing the results
NB! Test results are calculated arithmetic averages of 100.000 copy operations and unit of measurement is millisecond.
Problem
In some application we have business objects and DTO-s that have matching attributes or properties that we need to copy from one object to another. There can be many reasons why we cannot instantiate new objects and do updates to database based on new object. By example, if you use NHibernate as O/R mapper and you have ASP.NET MVC web application then you have to copy values from business objects to DTO-s when user saves exitsing or inserts new object. That’s because NHibernate monitors object and makes some magic with it. The other reason is even simpler – DTO-s generated for data transfer from view to controller cannot build up business objects automatically (we have manager ID field on form but how to load manager?).
There are also another scenarios when we need to copy values of properties from one object to another, by example web services that use DTO-s to map to and from business objects.
NB! We can write code that maps from type to type but we can also automate this task. By example, you can use AutoMapper like shown in Shiju Varghese’s blog posting View Model pattern and AutoMapper in ASP.NET MVC Applications. You can find also other tools if you make some searching in internet.
In this posting I will not use any tools but I show you how to write effective code that does such mappings. If you are planning systems that go to public then it is recommended to use some good external libraries for that.
Detecting matching properties
As a first thing we have to detect matching properties of two objects. We can use the following method for this. In this example I am copying only value-types and strings. Of course, you can modify this code to fit your needs.
public static 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.IsPublic &&
t.PropertyType.IsPublic
s.PropertyType == t.PropertyType &&
(
(s.PropertyType.IsValueType &&
t.PropertyType.IsValueType
) ||
(s.PropertyType == typeof(string) &&
t.PropertyType == typeof(string)
)
)
select new PropertyMap
{
SourceProperty = s,
TargetProperty = t
}).ToList();
return properties;
}
PropertyMap is the class that contains matching properties.
public class PropertyMap
{
public PropertyInfo SourceProperty { get; set; }
public PropertyInfo TargetProperty { get; set; }
}
In the LINQ query above we make sure that we don’t read write-only properties and we don’t write values to read-only properties. Also we exclude private properties so we don’t mess up something.
Copying matching properties – non-optimized version
Our first version of properties copying code is straight-forward and we don’t use any optimizations. We just want to see so called zero-results to get some base metric for optimizations. So, here is the code that does exactly what we need.
public static void CopyProperties(object source, object target)
{
var sourceType = source.GetType();
var propMap = GetMatchingProperties(sourceType, targetType);
for(var i=0; i<propMap.Count; i++)
{
var prop = propMap[i];
var sourceValue = prop.SourceProperty.GetValue(source,
null);
prop.TargetProperty.SetValue(target, sourceValue, null);
}
}
This method gave 0,0403 milliseconds per copy operation.
Copying matching properties – caching property map
Now let’s take a look at our hey-it-works! version of code we just wrote. There is one repeated step we can avoid – detecting matching properties. We don’t need to run it more than once if we cache the results. After adding this optimization we get the following code.
private static Dictionary<string, PropertyMap[]> _maps =
new Dictionary<string, PropertyMap[]>();
public static void AddPropertyMap<T, TU>()
{
var props = GetMatchingProperties(typeof (T), typeof (TU));
var className = GetClassName(typeof(T), typeof(TU));
_maps.Add(className, props.ToArray());
}
public static void CopyMatchingCachedProperties(object source,
object target)
{
var className = GetClassName(source.GetType(),
target.GetType());
var propMap = _maps[className];
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);
}
}
public static string GetClassName(Type sourceType,
Type targetType)
{
var className = "Copy_";
className += sourceType.FullName.Replace(".", "_");
className += "_";
className += targetType.FullName.Replace(".", "_");
return className;
}
This method gives us way better performance: 0,0247 milliseconds. It is about 1.6 times better than unoptimized result.
Copying matching properties – generating dynamic code
Now let’s remember all those ghost stories about reflection and performance. Reflection is powerful tool but it comes with its own cost. As our first optimization showed us we 2x gained performance boost when we applied caching of property maps.
Now let’s replace also copying code with something else. We can use dynamically generated and compiled code that copies values from one property to another line by line. Same stuff that we otherwise should write manually. We will create class per types, compile it and cache it. If we need to copy property values from one object to another second time and object types are same then we can run static method from cache without any additional actions.
private static readonly Dictionary<string, Type> Comp = _
new Dictionary<string, Type>();
public static void CopyWithDom<T, TU>(T source, TU target)
{
var className = GetClassName(typeof (T), typeof (TU));
var flags = BindingFlags.Public | BindingFlags.Static |
BindingFlags.InvokeMethod;
var args = new object[] {source, target};
Comp[className].InvokeMember("CopyProps", flags, null,
null, args);
}
public static void GenerateCopyClass<T, TU>()
{
var sourceType = typeof(T);
var targetType = typeof (TU);
var className = GetClassName(typeof (T), typeof (TU));
if (Comp.ContainsKey(className))
return;
var builder = new StringBuilder();
builder.Append("namespace Copy {\r\n");
builder.Append(" public class ");
builder.Append(className);
builder.Append(" {\r\n");
builder.Append(" public static void CopyProps(");
builder.Append(sourceType.FullName);
builder.Append(" source, ");
builder.Append(targetType.FullName);
builder.Append(" target) {\r\n");
var map = GetMatchingProperties(sourceType, targetType);
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." + className);
Comp.Add(className, copierType);
}
And now we have real killer result: 0,0055 milliseconds! This is ~4.5 times faster than caching property map and still using reflection to copy values and ~7.3 times faster than our first method. We wrote more code but we got way better performance than before.
Comparing results
As we have now about 1000 words let’s take a picture that explains more. Here is our results drawn out as bar chart.
We can see that unoptimized code is horror when compared to dynamic code. The result on chart tells us clearly that our last optimization gave us very good results.
Conclusion
Although reflection can be very expensive thing to use it is like electricity – it kills fools and helps smart ones. Through this example we optimized code step by step avoiding repeating things we already done. In the last optimization we were static and dynamic at same time – we created dynamically code that statically assigns values from one object to another and we used reflection to call this method.
As you can see from this example the ability to generate and compile source code on the fly can be very useful thing. We raised the speed of our copy method about 7.5 times using dynamically generated code. By the way, the win in performance is even greater when objects have more than 4-5 properties to copy.