Sunday, May 15, 2011

Expressionistic copying

Recently I had the need of cloning a number of object, traditionally you’d have to use reflection based techniques if you wanted a generic solution, (unless you go really hardcore with Reflection.Emit)

The sample code below only accounts for public properties, but in many cases that isn’t enough.

Given the performance implications of using reflection, and the fact that you are allowed to include blocks in expressions in .NET 4 I figured I’d do a small experiment and micro-benchmark comparing these two approaches:

I have an entity I want to copy like so

public class Entity
{
public int Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
public int Foo2 { get; set; }
public string Bar2 { get; set; }
public string Baz2 { get; set; }
public int Foo3 { get; set; }
public string Bar3 { get; set; }
public string Baz3 { get; set; }
}

I implemented reflection based copy like so (with some room for optimizations):



public class ReflectionObjectCopier : IObjectCopier
{
#region IObjectCopier Members

public void Copy<T>(T fromObj, T toObj)
{
var properties = from prop in fromObj.GetType().GetProperties(BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.FlattenHierarchy)
where prop.CanRead && prop.CanWrite
select prop;
foreach (PropertyInfo pi in properties)
{
pi.SetValue(toObj, pi.GetValue(fromObj, null), null);
}
}

#endregion
}


When using this copying 10 000 objects takes 250ms or so.



However, if I create an expression that performs the copying directly it only takes 10ms (with subsequent testruns only taking 4, when reusing the cached expression). That is a pretty decent saving if you ask me.



public class ExpressionObjectCopier : IObjectCopier
{
Dictionary<Type, object> cache = new Dictionary<Type, object>();

Action<T,T> CreateCopier<T>()
{
ParameterExpression fromParam = Expression.Parameter(typeof(T), "from");
ParameterExpression toParam = Expression.Parameter(typeof(T), "to");

LambdaExpression exp = Expression.Lambda(typeof(Action<T, T>),
Expression.Block(
from prop in typeof(T).GetProperties(BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.FlattenHierarchy)
where prop.CanRead && prop.CanWrite
select Expression.Assign(Expression.Property(toParam, prop),
Expression.Property(fromParam, prop))
),
fromParam,
toParam);

Action<T, T> copier = (Action<T, T>)exp.Compile();
cache[typeof(T)] = copier;
return copier;
}

#region IObjectCopier Members

public void Copy<T>(T fromObj, T toObj)
{
object action;
Action<T,T> copier;
if (!cache.TryGetValue(fromObj.GetType(), out action))
{
copier = CreateCopier<T>();
}
else
{
copier = (Action<T, T>)action;
}


copier(fromObj, toObj);
}

#endregion
}


The above code obviously isn’t production quality…