More on Late-Bound Invocations with Expression Trees

Recently, I went on a bit of a tear in the AutoMapper trying to improve performance.  Besides the threading issues I introduced (dictionary lookups are NOT thread safe, in case you’re wondering), I looked at improving the performance of the reflection piece of AutoMapper.  At its core, I map various kinds of MemberInfo objects on source types to MemberInfo objects on destination types.  The semantic model is a little more involved of course, but that’s the basic model of going from one complex type to another.

I was quite dreading speeding up the reflection piece, not because IL generation and reflection emit isn’t interesting and all that, but it all looks quite ugly and hard to maintain.  Until I stumbled upon Nate Kohari’s post on late-bound invocations via expression trees, I was avoiding that piece.  The basic idea is that expression trees, when compiled, are able to use regular reflection MemberInfo objects to generate the right IL at runtime to do what you normally do at compile time.  If you want a good sample of how to do IL, take a look at the expression tree compilation code in Reflector.

His example worked great for methods, but AutoMapper also supports properties and fields.  To achieve the same result, I needed first to define a couple more delegate types to capture the kind of call you would do with fields and properties:

public delegate object LateBoundProperty(object target);
public delegate object LateBoundField(object target);

Next, I just needed to create the expression tree for calling a field and property.  The property one is this:

public static LateBoundProperty Create(PropertyInfo property)
{
    ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "target");

    MemberExpression member = Expression.Property(Expression.Convert(instanceParameter, property.DeclaringType), property);

    Expression<LateBoundProperty> lambda = Expression.Lambda<LateBoundProperty>(
        Expression.Convert(member, typeof(object)),
        instanceParameter
        );

    return lambda.Compile();
}

I first create an expression that represents a call to a property (the MemberExpression instance).  Next, I need to wrap that MemberExpression call with a LambdaExpression, of type LateBoundProperty, so that I can execute the resulting delegate against any object I want.  The one for a field is quite similar:

public static LateBoundField Create(FieldInfo field)
{
    ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "target");

    MemberExpression member = Expression.Field(Expression.Convert(instanceParameter, field.DeclaringType), field);

    Expression<LateBoundField> lambda = Expression.Lambda<LateBoundField>(
        Expression.Convert(member, typeof(object)),
        instanceParameter
        );

    return lambda.Compile();
}

I still have a MemberExpression, but I use the Field method to create it instead.  It’s a little func-y looking, but here is what that above expression would look like, if I knew the target type at compile time:

Expression<Func<Order, decimal>> expr = o => o.Total;

I have to jump through a few more hoops, as I don’t know the in and out times at compile time, it’s all stored as reflection information.  The expression tree building doesn’t care anyway, as the above lambda syntax is merely syntactic sugar for what we wrote earlier.  But how does this perform?  As Rick Strahl points out, it only makes sense after many iterations.  But I can then do fun things like this:

public class Order
{
    public decimal Total { get; set; }
}

[Test]
public void Sample()
{
    PropertyInfo propertyInfo = typeof (Order).GetProperty("Total");

    var lateBoundProperty = DelegateFactory.Create(propertyInfo);

    var order = new Order
        {
            Total = 50m
        };

    var total = lateBoundProperty(order);

    total.ShouldEqual(50m);
}

Since AutoMapper already has all of the reflection information cached on a per app-domain basis (that’s why you should only configure once), I can easily go through all of the reflected information and pre-compile all of the late-bound fields, properties and methods, so that they execute very fast during a mapping operation.

So what’s the downside?  Startup is slower now, as it is CPU intensive to configure AutoMapper now.  But compared to the startup time of NHibernate and StructureMap in our applications, it does not add that much more to the total startup time.  Since we’re using AutoMapper in a web application, the we feel just as confident about its configuration time as we would NHibernate.

Unfortunately, expression trees in .NET 3.0 do not support setting fields and properties.  But since that’s changing in .NET 4.0, I think I’ll just wait to optimize the destination member assignment part of AutoMapper until then.

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.
  • http://www.lostechies.com/members/gnschenker/default.aspx Gabriel N. Schenker

    nice work!

  • http://www.lostechies.com/members/phatboyg/default.aspx Chris Patterson

    You CAN set properties, you cannot set fields.

    Check out Magnum.Reflection.FastProperty for the details as that’s what the mapper tools in Magnum use. It’s also how our CustomXmlSerializer works to serialize and recover properties from XML very quickly (over 10,000 msgs/second per CPU core).

    You can call methods in expressions, which includes the GetGetMethod and GetSetMethod(true) from the PropertyInfo.

    Check it out man…

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

    @Chris

    Thanks for the heads up! I need a cheat sheet for those expression trees, I always miss something. Doesn’t help that the expression types don’t match the ExpressionType enumeration either.