Late-Bound Invocations with DynamicMethod
When I was looking at improving AutoMapper performance, I initially focused on just the “getter” side of the mapping equation. At its core, you map between types by getting a value from one member and setting it on the other. I was already familiar with expression trees, so I went with an extension of Nate Kohari’s method of doing code generation by using expression trees to do late-bound invocation. However, you can’t really do this with setters. This won’t compile, for example:
Expression<Action<Source, int>> expr = (src, value) => src.Value = value;
You’ll get a fun compile error of:
error CS0832: An expression tree may not contain an assignment operator
Now, it’s possible to generate an expression tree to call a setter for a property, as a setter is really just method underneath the covers. But it’s not possible in C# 3.0 to set fields with expression trees. For this, I need another approach, so I decided to get my feet wet with Lightweight Code Generation using the DynamicMethod approach.
Getting a template
DynamicMethod works by compiling IL into a strongly-typed delegate. You code the IL however you like with an IL generator, then call DynamicMethod to create a delegate for you. In my case, I wanted a delegate to set a property value on an object. But because types aren’t known at compile-time in AutoMapper, only at runtime, my generated method must only work in terms of “object”. To get an idea of the IL I wanted to write, I just created a method that did exactly what I wanted, for some sample types:
private void DoItWithProperty(object source, object value) { ((Source)source).Value = (int)value; }
Notice that I’ll have to handle unboxing here, the value passed in could be anything, a reference or a value type. That also means that boxing happens when calling this method. But, boxing is just a way of life in a world where I have to deal with types and methods at runtime. To get a template, I viewed this compiled method in Reflector:
I don’t really know what all of these IL codes mean (any of them, actually), but I can make a meaningful guess at them. I can see the casting and unboxing, as well as calling the setter method. Now that I have a template for my method, I can create a more generalized version for my needs.
IL generation with DynamicMethod
To get my IL generation going, I wanted a named delegate type for the result of compiling my methods:
internal delegate void LateBoundFieldSet(object target, object value); internal delegate void LateBoundPropertySet(object target, object value);
These two delegates will hold functions that can take any object and set the correct property/field using the value supplied. I wanted a factory to generate setters given any PropertyInfo or FieldInfo I give it, since these are the things I’m working with when mapping. First, I’ll need my DynamicMethod:
public static LateBoundPropertySet CreateSet(PropertyInfo property) { var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(object), typeof(object) }, true);
I don’t need to name my dynamic method, but I do anyway. The next couple of parameters describe the signature of the method I’m creating, which is a void (object, object) method. The final parameter lets me skip any kinds of visibility checks, so I can get access to private accessors and whatnot.
With my DynamicMethod ready, I get the IL generator for it:
var gen = method.GetILGenerator();
And write the IL I need using this IL generator:
var sourceType = property.DeclaringType; var setter = property.GetSetMethod(true); gen.Emit(OpCodes.Ldarg_0); // Load input to stack gen.Emit(OpCodes.Castclass, sourceType); // Cast to source type gen.Emit(OpCodes.Ldarg_1); // Load value to stack gen.Emit(OpCodes.Unbox_Any, property.PropertyType); // Unbox the value to its proper value type gen.Emit(OpCodes.Callvirt, setter); // Call the setter method gen.Emit(OpCodes.Ret);
I put a bunch of comments in, as I can’t read IL and grok a program from it. Because I use the PropertyInfo’s types for the correct casting operations, my DynamicMethod will be tailored to exactly what my destination type and member types are. With the correct IL in, all that’s left is to create a delegate from my DynamicMethod and return it:
var result = (LateBoundPropertySet)method.CreateDelegate(typeof(LateBoundPropertySet)); return result;
With that handle to the LateBoundPropertySet delegate, I can do very efficient late-bound sets, where I only would know the types at runtime:
var sourceType = typeof (Source); PropertyInfo property = sourceType.GetProperty("Value"); LateBoundPropertySet callback = DelegateFactory.CreateSet(property); var source = new Source(); callback(source, 5); source.Value.ShouldEqual(5);
When I did some profiling, I found that the strongly-typed delegate (i.e., Action<Source, int>) was very close to the runtime of just setting directly. It was also on par with a lot of the numbers I read online. However, with the boxing/unboxing I have to do, I get not as good numbers, but waaaay better than reflection:
Raw:80000 MethodInfo:15780000 LCG:330000
Numbers are in ticks. While raw setters are about 200 times faster than plain old reflection, LCG clocks in at around 50 times faster. That’s a lot faster, but the boxing and unboxing winds up taking quite a bit more time, at least in the case of value types.
The completed code
Here’s the complete factory methods for setting both fields and properties:
public delegate void LateBoundFieldSet(object target, object value); public delegate void LateBoundPropertySet(object target, object value); public static LateBoundFieldSet CreateSet(FieldInfo field) { var sourceType = field.DeclaringType; var method = new DynamicMethod("Set" + field.Name, null, new[] { typeof(object), typeof(object) }, true); var gen = method.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); // Load input to stack gen.Emit(OpCodes.Castclass, sourceType); // Cast to source type gen.Emit(OpCodes.Ldarg_1); // Load value to stack gen.Emit(OpCodes.Unbox_Any, field.FieldType); // Unbox the value to its proper value type gen.Emit(OpCodes.Stfld, field); // Set the value to the input field gen.Emit(OpCodes.Ret); var callback = (LateBoundFieldSet)method.CreateDelegate(typeof(LateBoundFieldSet)); return callback; } public static LateBoundPropertySet CreateSet(PropertyInfo property) { var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(object), typeof(object) }, true); var gen = method.GetILGenerator(); var sourceType = property.DeclaringType; var setter = property.GetSetMethod(true); gen.Emit(OpCodes.Ldarg_0); // Load input to stack gen.Emit(OpCodes.Castclass, sourceType); // Cast to source type gen.Emit(OpCodes.Ldarg_1); // Load value to stack gen.Emit(OpCodes.Unbox_Any, property.PropertyType); // Unbox the value to its proper value type gen.Emit(OpCodes.Callvirt, setter); // Call the setter method gen.Emit(OpCodes.Ret); var result = (LateBoundPropertySet)method.CreateDelegate(typeof(LateBoundPropertySet)); return result; }
Fields were slightly different, but I followed the same pattern of 1) write a template method, 2) view in Reflector and 3) transpose IL code into the IL generator. This optimization drastically lowered the time AutoMapper spends on actually setting values, to the point where I could focus on other places to optimize.
What’s interesting about this code is that you could extend it even more to translate Expression<Func<TObject, TMember» into late-bound invocation methods, potentially greatly helping out those times where you’re using a lot of strongly-typed reflection to do both metadata and execution.