X

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:

  1. Stating the problem – when we need to copy property values
  2. Detecting matching properties of two objects using reflection and LINQ
  3. Copying matching properties (no optimizations)
  4. Copying matching properties (lightweight optimizations)
  5. Copying matching properties (heavily optimized)
  6. 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 targetType = target.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.

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

View Comments (7)

  • Lightweight code generation requires knowledge of the CLI. It sometimes requires more steps to perform simple operations as well that would only take 1 line of code in C#. And most of us already know C#, but not the CLI opcodes. Since the results of code generation are cached, the hit's only up front, so there's not really any noteworthy performance concerns between one approach over the other (I think). I only knew about LCG before, but I'm digging this approach, thanks!

  • Thanks for feedback guys! :)

    Lightweight code generation and unmanaged code are next things that I will try out. It is damn interesting to find out how much faster this code can go without losing generality.

  • @Ron

    I used to the think the same thing, but you really don't need that much knowledge of the CLI. Just a couple of opcodes at most, and an understanding of what a stack based machine is and how it works. This was my first foray into LCG and it was a cinch. I even wrote the copy code in C#, and then used Reflector to view what it would look like in IL; that helped a lot.

    And while it's true that one line of C# can be made up of many IL instructions, in this case, you almost have 100 lines of code to generate a C# class with a copy method, whereas with the dynamic IL copy generator, it's about 10 lines. I haven't run benchmarks but I'd bet it also compiles faster as well.

  • In your first snippet, you don't need the following code:

    s.PropertyType.IsPublic && t.PropertyType.IsPublic

    Reflection gets only the public properties.

    Arun

  • The problem with the dynamic solution is that the compiled assembly cannot be unloaded. This can cause memory problems, especially if the application is long-running and if many Copy classes are generated.

    The only way to circumvent this, is to generate all classes in separate application domains and unload those domains when they are not needed anymore.

  • Thanks for feedback, Jimmy! I am not sure how same is the SAME but I will try it out for sure. Again, thanks for pointing me out to expression trees. :)

Related Post