Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Creating Property Set Expression Trees In A Developer Friendly Way

5.00/5 (1 vote)
12 Jan 2011CPOL1 min read 20.1K  
How to create property set expression trees in a developer friendly way

In a previous post, I showed how to create expression trees to set properties on an object.StatCounter

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 to help develop some kind of command pattern framework that used developer friendly lambdas to generate property set expression trees.

Simply put, given this entity class:

C#
public class Person
{
    public string Name { get; set; }
}

The person in question wanted to write code like this:

C#
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 retrieving the value to assign to the property:

C#
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 to retrieve the value to assign to the property:

C#
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 from 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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)