Expression Trees: Part 2 – Fetching Properties


This is part of a series of blog post about expression trees in .NET 4.0.  The past blog entries are:

In this article, I’ll cover using Expression Trees (ET) for fetching properties.

In this article, I’ll use the following classes:

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public Address Address { get; set; }
    public Project Project { get; set; }
}

public struct Address
{
    public int StreetNumber { get; set; }
    public string StreetName { get; set; }
}

public class Project
{
    public string ProjectName { get; set; }
    public decimal Budget { get; set; }
}

Simply notice that Address is a structure, hence it can’t be null.

First, let’s look at the trivial case:

Expression<Func<Employee, string>> firstNameExp = e => e.FirstName;
var firstNameFetcher = firstNameExp.Compile();
var employee = new Employee
{
    FirstName = "John",
    LastName = "Smith",
    DateOfBirth = DateTime.Now
};

Console.WriteLine(firstNameFetcher(employee));

Here firstNameExp represents an expression tree taking an employee and fetching its first name.  We can compile it and extract a property from an instance of an employee.  Trivial indeed.

The last example uses an expression tree as a delegate.  But an expression tree is much more expressive than a delegate:  it allows use to look inside the code composing it.  Let’s take a similar property fetching expression tree and extract the property path (the name of each property traversed) from it.

This will require a few methods to deliver.  At the heart of this functionality, we need to enumerate the property accessed by the expression.  For this, I can’t simply use a LINQ query since the expression tree is…  a tree, not a list, so it stores its elements by recursive composition.  Instead, I choose to leverage the powerful C# yield construct:

private static IEnumerable<MemberExpression> GetMemberExpressions(Expression e)
{
    MemberExpression memberExpression;

    while ((memberExpression = e as MemberExpression) != null)
    {
        yield return memberExpression;

        e = memberExpression.Expression;
    }
}

This method returns an enumeration of MemberExpression.  It returns them from right to left if we consider how the expression is written.  For instance, for m.p1.p2.p3, GetMemberExpressions would return {(m.p1.p2).p3, (m.p1).p2, (m).p1}.  So we now need a method to reverse this enumeration and pull out the property names.  Here we can use LINQ and we gladly do:

private static string PathFinder(LambdaExpression e)
{
    var expressions = GetMemberExpressions(e.Body);
    var pathParts = from memberExp in expressions.Reverse()
                    select memberExp.Member.Name;
    var path = string.Join(".", pathParts);

    return path;
}

We can now use this method to introspect lambda expressions:

Expression<Func<Employee, string>> firstNameExp = e => e.FirstName;
Expression<Func<Employee, int>> streetNumberExp = e => e.Address.StreetNumber;
Expression<Func<Employee, string>> projectNameExp = e => e.Project.ProjectName;

Console.WriteLine(PathFinder(firstNameExp));
Console.WriteLine(PathFinder(streetNumberExp));
Console.WriteLine(PathFinder(projectNameExp));

This code displays, unsurprisingly:

  • FirstName
  • Address.StreetNumber
  • Project.ProjectName

What we just did is to look inside an expression tree to extract information from it.  We can also modify this expression tree.  An interesting example would be to harden a property fetch.  Here, by harden, I mean checking for null values.  For instance, if I provide the lambda expression:

m => m.p1.p2

I would like to automatically generate the following:

m => m!=null ? (m.p1!=null ? m.p1.p2 : null) : null;

The difference between the former an the latter is that the former will throw an exception if m or m.p1 is null while the latter simply returns null, hence hardening it.  There might be different way to do it, you might not want to return null but instead be informed that it’s impossible to fetch the property for instance.  All cases would go about similar ways to be implemented.

The simplest way to implement this transformation is to use the visitor pattern we discussed in the previous post:

private class SafeMemberAccessVisitor : ExpressionVisitor
{
    protected override Expression VisitMember(MemberExpression node)
    {
        //    Only relevant if we are trying to access
        //    a member from a class (nullable)
        if (node.Member.DeclaringType.IsClass)
        {    //    We harden the expression we’re about to fetch a member from
            var safeSubExpression = Visit(node.Expression);
            //    Represents the e != null expression
            var test = Expression.NotEqual(
                safeSubExpression,
                Expression.Constant(null));
            //    Represents the e!=null ? e.p : null expression
            var inlineIf = Expression.Condition(
                test,
                node,
                Expression.Default(node.Type));

            return inlineIf;
        }

        //    We rely on base implementation otherwise
        return base.VisitMember(node);
    }

Here we let the visitor base class navigate the expression tree recursively and find the member-access expressions for us.

Using the visitor is simple enough:

private static Expression<Func<M, R>> MakeSafe<M, R>(Expression<Func<M, R>> unsafeExp)
{
    var visitor = new SafeMemberAccessVisitor();
    var modifiedIncrementExp = visitor.Visit(unsafeExp) as Expression<Func<M, R>>;

    return modifiedIncrementExp;
}

We can then use this method:

var firstNameExp = MakeSafe((Employee e) => e.FirstName);
var streetNumberExp = MakeSafe((Employee e) => e.Address.StreetNumber);
var projectNameExp = MakeSafe((Employee e) => e.Project.ProjectName);

Console.WriteLine(firstNameExp);
Console.WriteLine(streetNumberExp);
Console.WriteLine(projectNameExp);

Expression.ToString returns a Visual Basic representation of an expression, so we get:

  • e => IIF((e != null), e.FirstName, default(String))
  • e => IIF((e != null), e.Address, default(Address)).StreetNumber
  • e => IIF((IIF((e != null), e.Project, default(Project)) != null), e.Project.ProjectName, default(String))

Those are correct.  We are using default values for types as oppose to null.  Default was introduced in .NET 2.0 to handle generics.  A reference type default value is null while a value type has a specific default value (e.g. for integers, 0).

We can use those hardened expression trees on real cases.  We first create an instance of employee and compile the expression trees into delegates:

var employee = new Employee
{
    FirstName = "John",
    LastName = "Smith",
    DateOfBirth = DateTime.Now,
    Address = new Address { StreetName = "Baker Street", StreetNumber = 222 },
    Project = new Project { Budget = 6000, ProjectName = "POC" }
};
var firstNameFetcher = firstNameExp.Compile();
var streetNumberFetcher = streetNumberExp.Compile();
var projectNameFetcher = projectNameExp.Compile();

Then we can test the delegate in different scenarios:

Console.WriteLine(firstNameFetcher(employee));  //  John
Console.WriteLine(streetNumberFetcher(employee));  //  222
Console.WriteLine(projectNameFetcher(employee));   //  POC

Console.WriteLine(projectNameFetcher(null));  //  null
Console.WriteLine(streetNumberFetcher(null));  // 0
employee.Project = null;
Console.WriteLine(projectNameFetcher(employee));  //  null
Console.WriteLine(projectNameFetcher(null));   //  null

This really shows the power of expression trees.  A consumer can describe an intent with code and we can manipulate that intent into real compiled code on the fly.


4 thoughts on “Expression Trees: Part 2 – Fetching Properties

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s