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 }); }
This code has at least two major problems.
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.
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.
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.
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.
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 :)