Sunday, July 10, 2011

The Object Copier, revisited.

 

We saw in the previous post how we could improve the maintainability of reflection-based code by building lambdas using reflection, and then gain the performance benefits by applying the lambdas at execution time.

Taking the idea a step further, we can easily see that what we are really doing is generating a function to do the copy operation on the entire object and applying the copy function.

(T _source, T _destination) =>
{
_destination.Id = _source.Id;
_destination.Name = _source.Name;
...
}

Based on the approach we took last time, this is easily achievable by wrapping the individual assignments into a block statement, wiring through the parameter expressions from the block into the individual lambdas, and then compiling out a full copy. Thusly:

public static class Copier
{
public static void CopyTo<T>(this T source, T destination) { CopyClosure<T>.Copy(source, destination); }

private static class CopyClosure<T>
{
private static Action<T, T> BuildCopierLambda()
{
var sourceParameterExpression = Expression.Parameter(typeof(T), "_source");
var destinationParameterExpression = Expression.Parameter(typeof(T), "_destination");

var properties = typeof(T).GetProperties();
var expressions = properties.Select(_pi => Expression.Assign(Expression.Property(destinationParameterExpression, _pi), Expression.Property(sourceParameterExpression, _pi)));

var blockExpression = Expression.Block(expressions);
var lambdaExpression = Expression.Lambda<Action<T, T>>(blockExpression, sourceParameterExpression, destinationParameterExpression);

return lambdaExpression.Compile();
}

private static readonly Action<T, T> _copy = BuildCopierLambda();
public static void Copy(T source, T destination) { _copy(source, destination); }
}
}


There are a few things to note about the code:


The outer, public, static  Copier class is non-generic to allow the CopyTo operation to be defined as an extension property on generic type T.


The compiler will create a special, unique CopyClosure class for each type T. This ensures that the _copy Action is unique for each type T. Also, the _copy Action is initialized in a thread-safe manner, because the compiler guarantees thread-safety on field initializers.


This approach actually improves performance, because there is no run-time iteration!


copy_block_expression


Stay tuned for more improvements…