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:

image

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.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in C#. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Artem Govorov

    Hi Jimmy,

    I had a similar task and here is how I solved it without IL:

    public delegate void LateBoundPropertySet(object target, object value);

    public static LateBoundPropertySet CreateSet(PropertyInfo property)
    {
    var setterType = typeof(Action< ,>).MakeGenericType(property.DeclaringType, property.PropertyType);
    var propertyWriter = typeof(PropertyWriter< ,>).MakeGenericType(property.DeclaringType, property.PropertyType);
    var setterDelegate = Delegate.CreateDelegate(setterType, property.GetSetMethod());
    var writer = (IPropertyWriter)Activator.CreateInstance(propertyWriter, setterDelegate);
    return writer.SetValue;
    }

    private interface IPropertyWriter
    {
    void SetValue(object instance, object value);
    }

    private class PropertyWriter : IPropertyWriter
    {
    private readonly Action
    _setValueDelegate;

    public PropertyWriter(Action setValueDelegate)
    {
    _setValueDelegate = setValueDelegate;
    }

    public void SetValue(object instance, object value)
    {
    _setValueDelegate((TInstance)instance, (TProperty)value);
    }
    }

  • http://techmikael.blogspot.com/ Mikael Svenson

    You could generate several DynamicMethods which took the correct types in, instead of doing the boxing/unboxing. Then keep a list of them and invoke the proper one as you pass in source/dest types.

    class DelegateFactory

    DelegateFactory.LateBoundPropertySet callback = DelegateFactory.CreateSet(property);

    It might be easier to generate those using CodeDomProvider instead as you don’t have to type IL.

  • Michael Sevestre

    Thanks for sharing Jimmy,
    Instead of doing the unboxing, you could use sthg like that:
    gen.Emit(OpCodes.Ldarg_1);
    UnboxIfNeeded(field.FieldType, gen);
    gen.Emit(OpCodes.Stfld, field);

    where UnboxIfNeeded is
    private static void UnboxIfNeeded(Type type, ILGenerator generator)
    {
    if (type.IsValueType)
    {
    generator.Emit(OpCodes.Unbox_Any, type);
    }
    }

    one “if” more but no unboxing if not needed

    FYI:
    I ran into some issue using some IL generation with DynamicMethod in the past when dealing with private field.
    So I tried to use your CreateSet(FieldInfo field) method this morning with a purely private field (type int, no setter/getter) to see how it would behave. I got following error
    “operation could destabilize the runtime”…. scary :-)

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Artem

    Yeah, I tried Delegate.CreateDelegate, but found it to be slower. Have you run any numbers on it?

    @Mikael

    The problem is that AutoMapper doesn’t know about these types at compile time, only runtime. All I have to work with is MemberInfo objects. To AutoMapper, everything is an object.

    @Michael

    Thanks! I think I still need to use unbox.any, as I have to cast the incoming type to the type of the setter. Unbox.any either unboxes to the value type, or does castclass if it’s a reference type, so it’s a two-for-one deal.

  • Artem Govorov

    @Jimmy,

    I have done some testing: your solution is ~2 times faster on value types, very insignificantly faster on reference types.

    On the other hand, in my solution creation of LateBoundPropertySet is ~7 times faster comparing to yours.

    I guess for Automapper scenarios LateBoundPropertySet delegate usage performance is much more important than creation performance, so your solution is better for Automapper. Anyway, I suggested my alternative not so much from performance perspective, but just as an example how the same thing could be solved without IL generation (thus much simpler, easier to support and less error prone – though once written this piece of code doesn’t really need much support).

  • Edika

    Hi,
    I don’t know how AutoMapper works, but i’ve test your sample with my custom ORM. After some test I’ve notice my solution is much faster then this (in unit test 44 seconds Vs 81 seconds).

    In my ORM I’ve created a cache to store the needed information about PropertyInfo and FieldInfo of each business class of my application.
    When I need to set or get a value I simply used the stored PropertyInfo or FieldInfo retrieved from the cache to access or set the value. So it seems that the costs of the Reflection is not the SetValue or GetValue operation but only the GetPropertyInfo or GetFieldInfo.
    When I query the cache to get the business class information, if the information are not in the cache, ones are retrieved using Reflection and stored in the cache.
    In this way the Reflection are used only one time per business class.
    If you want I can post my example test project.

    Ciao

  • Edika

    Hi,
    I don’t know how AutoMapper works, but i’ve test your sample with my custom ORM. After some test I’ve notice my solution is much faster then this (in unit test 44 seconds Vs 81 seconds).

    In my ORM I’ve created a cache to store the needed information about PropertyInfo and FieldInfo of each business class of my application.
    When I need to set or get a value I simply used the stored PropertyInfo or FieldInfo retrieved from the cache to access or set the value. So it seems that the costs of the Reflection is not the SetValue or GetValue operation but only the GetPropertyInfo or GetFieldInfo.
    When I query the cache to get the business class information, if the information are not in the cache, ones are retrieved using Reflection and stored in the cache.
    In this way the Reflection are used only one time per business class.
    If you want I can post my example test project.

    Ciao

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Edika

    I suppose it was my fault for not posting the profiling code…but the profiling here was specifically a tight loop around only the reflection calls, not any of the AutoMapper API. I have done more profiling around the whole of AutoMapper, and the bottlenecks just aren’t the reflection stuff.

    The issue I run into is that many decisions on what to do are runtime and value (and not type) specific. Some of these I’m just stuck with.

  • Edika

    OK understood ;)

    Sorry for the double post….