Setting property without knowing target type at compile time

Sometimes there’s a need to set a property on an object of an unknown (or uncertain) at compile time type.
There’s a few ways to do this, all different in complexity and speed. I measured the time how long it takes for each method to set a value on a property 1 mln times. The time it takes may not seem high even when using Reflection, but keep in mind this is setting just a single property with a short string. In reality it’s likely to be noticeably slower.

Say, we have this class:

public class User
{
    public string Name { get; set; }
}

and we need to set the Name property.

User user = new User();

We can use Reflection:

user.GetType().GetProperty("Name").SetValue(user, "Bob", null);

This is simplest, but by far the slowest way, taking just over 500ms.

Reflection #2:
Cache the PropertyInfo:

PropertyInfo pi = user.GetType().GetProperty("Name");

for (int i = 0; i < (1 mln); i++)
    pi.SetValue(user, "Bob", null);

This cuts the time down to 320ms.

Reflection #3:
Almost same as #2, but instead of caching PropertyInfo I’ve tried to cache MethodInfo – wondered if it would make any difference.

MethodInfo mi = user.GetType().GetProperty("Name").GetSetMethod();
    mi.Invoke(user, new[] { "Bob" });

This is a ‘little’ faster than PropertyInfo, bringing time down to about 305ms.

Ok, now to the good stuff!
Ideally, I’d want something as close to user.Name = “Bob” as possible; this can be done using a run-time generated code.

First, a method that almost does it.

      

private static Action<TTargetType, TProperty> CreateSetter<TTargetType, TProperty>(Expression<Func<TTargetType, string>> propertyAccessor)
{
    MemberExpression mex = propertyAccessor.Body as MemberExpression;
    string propertyName = mex.Member.Name;

    ParameterExpression targetObjParamExpr = Expression.Parameter(typeof(TTargetType));
    ParameterExpression valueParamExpr = Expression.Parameter(typeof(TProperty));
    MemberExpression propertyExpr = Expression.Property(targetObjParamExpr, propertyName);
    BinaryExpression assignExpr = Expression.Assign(propertyExpr, valueParamExpr);

    return Expression.Lambda<Action<TTargetType, TProperty>>(assignExpr, targetObjParamExpr, valueParamExpr).Compile();
}

The call looks like this:

    Action<User, string> setter = CreateSetter<User, string>(x => x.Name);
    setter(user, "Bob");

It still requires to know the target type at compile time, so it won’t fit the requirements, but it demonstrates the use of Expression class, and how to pull a property name from an object using lambda expressions. This is great when you know the type beforehand, as I don’t have to hardcode the property name, and if it needs to be changed – it can be refactored, instead of doing search/replace.
Speed wise – this takes about 30ms, more than 10 times faster than reflection, and it’s about as fast as it will get.

OK, so now to the code that doesn’t require knowing the type upfront – pretty close to the code above (and thanks to Mark Gravell for hint):

public static Action<object, object> CreatePropertySetter(Type targetType, string propertyName)
{
    var target = Expression.Parameter(typeof(object), "obj");
    var value = Expression.Parameter(typeof(object), "value");
    var property = targetType.GetProperty(propertyName);
    var body = Expression.Assign(
        Expression.Property(Expression.Convert(target, property.DeclaringType), property),
        Expression.Convert(value, property.PropertyType));

    var lambda = Expression.Lambda<Action<object, object>>(body, target, value);
    return lambda.Compile();
}

The call:

Action<object, object> userNameSetter = CreatePropertySetter(user.GetType(), "Name");
  userNameSetter(user, "Bob");

It returns an Action<object, object>, where first object is the object to act upon (property holder), and second object is the new property value.
This is just as fast as previous solution, something in low 30ms.

One last option, again courtesy of Mark, is to use FastMember (look it up on Nuget). This would be the easiest solution, and it’s as fast as previous 2:

ObjectAccessor accessor = ObjectAccessor.Create(user);
accessor["Name"] = "Bob";

2 lines of code.. done. This also doesn’t require to create a number of delegates, each for specific property. This is great!

For comparison, when calling user.Name = “Bob”;, it takes under 10ms. So even with delegates it still takes 3 times longer, but, comparing to Reflection – this is much, much better.

Leave a Reply

Your email address will not be published. Required fields are marked *