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.
- It is finding properties and methods via reflection in a loop
- 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