Home > C#, expressions, lambdas, testing > Displaying a nested evaluation tree from Expression<Func>

Displaying a nested evaluation tree from Expression<Func>

Updated: The downloadable source (see link below) is now tidied up a bit, and also displays any embedded string comparisons in a similar way to NUnit.

This might be useful as a way to write Assert statements in tests. Instead of requiring many different forms of Assert to capture values and intents, we could just have one Assert that accepts a lambda:

public static void Assert(Expression<Func<bool>> expr)
    if (!(bool)Trace(expr.Body, 0))
        // throw an exception...

Now all we need is a Trace function. The challenge is making it include all the relevant information about the test, including the values being used. But this isn’t actually that hard, because you just recursively search through the tree of nested expressions, evaluating them all:

public static object Trace(Expression part, int indent)
    string indentString = new String(' ', indent*2);

    Console.Write(" == ");
    LambdaExpression lambda = Expression.Lambda(part);
    Delegate callable = lambda.Compile();
    object result = callable.DynamicInvoke(null);

    if (result is string)
        result = "\"" + result + "\"";


    var nestedExpressions = part
        .Where(p => typeof(Expression).IsAssignableFrom(p.PropertyType))
        .Select(p => (Expression)p.GetValue(part, null))
        .Where(x => (x != null) && !(x is ConstantExpression));

    if (nestedExpressions.Any())
        Console.WriteLine(" where");

        foreach (Expression nested in nestedExpressions)
            Trace(nested, indent + 1);


    return result;

I just use reflection and a bit of LINQ to hunt for nested expressions, so I don’t need to laboriously handle the various kinds of expression.

I don’t bother presenting the value of constants, because it would just explain to the user that 5 == 5, which isn’t very enlightening.

The GetExpressionText function is a helper to tidy up the output. Variables captured in the lambda have some nasty looking prefix to identify which compiler-generated class they belong to, but that’s unlikely to be useful here, so I strip it out:

public static string GetExpressionText(Expression expr)
    string text = expr.ToString();

    const string prefix = "value(";
    const string suffix = ").";

    StringBuilder builder = new StringBuilder();

    int start = 0;
    int pos = text.IndexOf(prefix);
    while (pos != -1)
        builder.Append(text.Substring(start, pos - start));
        start = text.IndexOf(suffix, pos) + suffix.Length;
        pos = text.IndexOf(prefix, start);

    if (start < text.Length)
        builder.Append(text.Substring(start, text.Length - start));

    return builder.ToString();

And now for a little demo:

int x = 3;
string t = "hi";
Assert(() => 5*x + (2 / t.Length) < 99);

Which produces the remarkably readable output:

(((5 * x) + (2 / t.Length)) < 99) == True where
  ((5 * x) + (2 / t.Length)) == 16 where
    (5 * x) == 15 where
      x == 3
    (2 / t.Length) == 1 where
      t.Length == 2 where
        t == "hi"

Obviously not a hugely strenuous test, but I suspect any other edge cases could easily be dealt with.

The way I’ve written Assert is just for demo purposes, because I wanted to see the trace regardless of whether the test passes or fails. In a real framework you’d only print out the trace if the test failed. Apart from making the successful output as short as possible (silence is golden) it would also help your tests run faster.

Download full source here: http://www.earwicker.com/blogfiles/ExpressionTracer.zip

  1. IDisposable
    March 23, 2009 at 10:28 pm

    Very nice idea!

    You can StringBuilder.Append a substring directly like:
    builder.Append(text, start, pos – start);

    Instead of calling:
    builder.Append(text.Substring(start, pos – start));

  2. May 13, 2013 at 9:24 pm

    Marc Gravell extended your approach to support parameters at http://stackoverflow.com/questions/697463/print-out-linq-expression-tree-hierarchy

  1. No trackbacks yet.

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 )

Google+ photo

You are commenting using your Google+ 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

%d bloggers like this: