Creating Property Set Expression Trees In A Developer Friendly Way
- by Paulo Morgado
In a previous post I showed how to create expression trees to set properties on an object. The way I did it was not very developer friendly. It involved explicitly creating the necessary expressions because the compiler won’t generate expression trees with property or field set expressions. Recently someone contacted me the help develop some kind of command pattern framework that used developer friendly lambdas to generate property set expression trees. Simply putting, given this entity class: public class Person
{
public string Name { get; set; }
}
The person in question wanted to write code like this:
var et = Set((Person p) => p.Name = "me");
Where et is the expression tree that represents the property assignment.
So, if we can’t do this, let’s try the next best thing that is splitting retrieving the property information from the retrieving the value to assign o the property:
var et = Set((Person p) => p.Name, () => "me");
And this is something that the compiler can handle.
The implementation of Set receives an expression to retrieve the property information from and another expression the retrieve the value to assign to the property:
public static Expression<Action<TEntity>> Set<TEntity, TValue>(
Expression<Func<TEntity, TValue>> propertyGetExpression,
Expression<Func<TValue>> valueExpression)
The implementation of this method gets the property information form the body of the property get expression (propertyGetExpression) and the value expression (valueExpression) to build an assign expression and builds a lambda expression using the same parameter of the property get expression as its parameter:
public static Expression<Action<TEntity>> Set<TEntity, TValue>(
Expression<Func<TEntity, TValue>> propertyGetExpression,
Expression<Func<TValue>> valueExpression)
{
var entityParameterExpression = (ParameterExpression)(((MemberExpression)(propertyGetExpression.Body)).Expression);
return Expression.Lambda<Action<TEntity>>(
Expression.Assign(propertyGetExpression.Body, valueExpression.Body),
entityParameterExpression);
}
And now we can use the expression to translate to another context or just compile and use it:
var et = Set((Person p) => p.Name, () => name);
Console.WriteLine(person.Name); // Prints: p => (p.Name = “me”)
var d = et.Compile();
d(person);
Console.WriteLine(person.Name); // Prints: me
It can even support closures:
var et = Set((Person p) => p.Name, () => name);
Console.WriteLine(person.Name); // Prints: p => (p.Name = value(<>c__DisplayClass0).name)
var d = et.Compile();
name = "me";
d(person);
Console.WriteLine(person.Name); // Prints: me
name = "you";
d(person);
Console.WriteLine(person.Name); // Prints: you
Not so useful in the intended scenario (but still possible) is building an expression tree that receives the value to assign to the property as a parameter:
public static Expression<Action<TEntity, TValue>> Set<TEntity, TValue>(Expression<Func<TEntity, TValue>> propertyGetExpression)
{
var entityParameterExpression = (ParameterExpression)(((MemberExpression)(propertyGetExpression.Body)).Expression);
var valueParameterExpression = Expression.Parameter(typeof(TValue));
return Expression.Lambda<Action<TEntity, TValue>>(
Expression.Assign(propertyGetExpression.Body, valueParameterExpression),
entityParameterExpression,
valueParameterExpression);
}
This new expression can be used like this:
var et = Set((Person p) => p.Name);
Console.WriteLine(person.Name); // Prints: (p, Param_0) => (p.Name = Param_0)
var d = et.Compile();
d(person, "me");
Console.WriteLine(person.Name); // Prints: me
d(person, "you");
Console.WriteLine(person.Name); // Prints: you
The only caveat is that we need to be able to write code to read the property in order to write to it.