共用方式為


Tip 54 – How to improve performance using Statement Expressions

Background:

While writing the update post in my Data Service Provider series I ended up writing this block of reflection code to copy properties values from one object to another:

foreach (var prop in resourceType
.Properties
.Where(p => (p.Kind & ResourcePropertyKind.Key)
!= ResourcePropertyKind.Key))
{
var clrProp = clrType
.GetProperties()
.Single(p => p.Name == prop.Name);
var defaultPropValue = clrProp
.GetGetMethod()
.Invoke(resetTemplate, new object[] { });
clrProp
.GetSetMethod()
.Invoke(resource, new object[] { defaultPropValue });
}

Problem:

This code has at least two major problems.

  1. It is finding properties and methods via reflection in a loop
  2. It is invoking those methods using reflection in a loop.

We could address (1) by storing all the property get / set methods in some cacheable data-structure.

But fixing (2) is a little more tricky, to make this code truly generic you need to use something like lightweight code-gen, or worse.

Solution:

Thankfully .NET 4.0 adds statement expressions. Which means you can create expressions that describe multi-line statements now, and yes those statements can do things like assignments.

Picture Borat saying ‘Nice…’

A quick search of the interweb revealed this excellent post on statement expressions by Bart, and that was enough to get me – and I hope you – to get excited.

Step 1 – Getting familiar with the API

Armed with my new found enthusiasm I decided to dip my toes in the water with something simple, namely trying to convert this into an expression:

Func<int> func = () => {
int n;
n=2;
return n;
};

It would be awesome if you could just do this:

Expression<Func<int>> expr = () => {
int n;
n = 2;
return n;
};

But unfortunately C# doesn’t support this today, instead you have to manually construct the expression, like this:

var n = Expression.Variable(typeof(int));
var expr = Expression.Lambda<Func<int>>
(
Expression.Block(
// int n;
new[] { n },
// n = 2;
Expression.Assign(
n,
Expression.Constant(2)
),
// return n;
n
)
);

Pretty easy huh.

Step 2 – Proving we can assign to a property

Now we’ve got a feel for the API, it’s time to start applying it to our problem.

What we need is a function that will modify an Object (in this case a product) by resetting one or more of its properties, like this:

Action<Product> baseReset = (Product p) => { p.Name = null; };

This code creates an equivalent action using the new expression APIs:

var parameter = Expression.Parameter(typeof(Product));
var resetExpr = Expression.Lambda<Action<Product>>(
Expression.Block(
Expression.Assign(
Expression.Property(parameter,"Name"),
Expression.Constant(null, typeof(string))
)
),
parameter
);
var reset = resetExpr.Compile();
var product = new Product { ID = 1, Name = "Foo" };
reset(product);

And sure enough after reset(product) is called the product Name is null.

Step 3 – Assigning to all non key properties

Now all we need to do is create a function that when given a particular CLR type will create an expression to reset all the non-key properties.

Actually collecting the list of properties and their intended values, isn’t interesting for this discussion, so lets imagine we’ve already got that information in a dictionary, like this:

var properties = new Dictionary<PropertyInfo, object>();
var productProperties = typeof(Product).GetProperties();
var nameProp = productProperties.Single(p => p.Name == "Name");
var costProp = productProperties.Single(p => p.Name == "Cost");
properties.Add(nameProp, null);
properties.Add(costProp, 0.0M);

Given this data-structure our job is to create an expression that has the same affect as this action:

Action<Product> baseReset = (Product p) => {
p.Name = null;
p.Cost = 0.0M;
};

First we need to create all the assignment expressions:

var parameter = Expression.Parameter(typeof(Product));
List<Expression> assignments = new List<Expression>();
foreach (var property in properties.Keys)
{
assignments.Add(Expression.Assign(
Expression.Property(parameter, property.Name),
Expression.Convert(
Expression.Constant(
properties[property],
property.PropertyType
)
)
);
}  

Next we feed the assignment expressions into a block inside a lambda, compile the whole thing and test out our nifty new function:

var resetExpr = Expression.Lambda<Action<Product>>(
Expression.Block(
assignments.ToArray()
),
parameter
);
var reset = resetExpr.Compile();
var product = new Product { ID = 1, Name = "Foo", Cost = 34.5M };
reset(product);
Debug.Assert(product.Name == null);
Debug.Assert(product.Cost == 0.0M);

As expected this works like a charm.

To solve the problem I had in my Update Post, we’d need a dictionary keyed on type, that we can used to store the reset action for a particular type. Then if a type’s reset action it isn’t found we just create it…

And our performance problem should be a thing of the past :)

Comments

  • Anonymous
    February 14, 2010
    Curious if you have performance numbers using Expressions over the nested reflection
  • Anonymous
    February 15, 2010
    @JohnI've got no perf numbers specific to this technique, but it should be a significant difference, its essentially the difference between normal code (once the expression is compiled) and reflection.Alex