In this article, we will talk about Expression Trees in C#, how to use them, and see some of their applications. We’ll also learn about their limitations and performance consideration. Although the topic may initially sound complex, it‚Äôs much simpler when we know a few concepts around it.

To download the source code for this article, you can visit our GitHub repository.

Let’s get started.¬†

What are Expression Trees?

Expression trees are a powerful feature of C# that allows us to represent code as data. They are a tree-like data structure where each node in the expression tree represents operations, variables, constants, and other code elements.

Expression trees enable us to manipulate code at runtime, such as compiling it to delegates, transforming it, or translating it to another language like SQL. We want to use expression trees for these reasons.

How to Create Expression Trees?

There are multiple ways to create expression trees. The easiest way is to create directly from the lambda expressions. The other way is to use the Expression class and manually create it. Let’s have a look at both of them.

Don't like the ads? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!

Create Expression Trees From Lambda Expression

Before proceeding, we should have a good understanding of Lambda Expressions.

Let’s create a simple lambda expression to find a sum of two numbers and later see how we can convert it to an Expression Tree:

Func<int, int, int> sum = (int number1, int number2) => number1 + number2;

The given lambda expression takes two parameters number1 and number2 of type integers and returns their sum as an integer. Hence, the inferred type of this expression will be Func<int, int, int>.

Now if we want to execute this lambda expression, we can do so by simply invoking it like a function:

sum(1, 2);

To convert the same lambda expression into an expression tree, let’s first create a new ExpressionTrees class and create a new method:

public static Expression<Func<int, int, int>> CreateExpressionTreeFromLambdaExpression()
{
    Expression<Func<int, int, int>> sumExpressionTree = (int number1, int number2) => number1 + number2;

    return sumExpressionTree;
}

We can convert it by simply assigning it to the  Expression<TDelegate> class where TDelegate is a generic delegate type parameter.

If we look closely, the right-hand side of the statement remains unchanged. But on the left-hand side, we replaced the TDeletegate generic delegate parameter of Expression<TDelegate> class with Func<int, int, int>.

We might wonder what’s the effect after making this change. Well, the compiler automatically converts the lambda expression into a tree-like data structure on assignment to the Expression<TDelegate> class.

Create Expression Tree Using Expression Tree API

Previously, we created an expression tree directly from a lambda expression. Now let’s look at how we can create it manually using the Expression class and its factory methods.

Let’s imagine the same lambda expression and build it as Expression Tree:

(int number1, int number2) => number1 + number2;

Let’s create another method:

public static Expression<Func<int, int, int>> CreateExpressionTreeUsingExpressionTreeClass()
{
    ParameterExpression param1 = Expression.Parameter(typeof(int), "number1");
    ParameterExpression param2 = Expression.Parameter(typeof(int), "number2");

    BinaryExpression sumBody = Expression.Add(param1, param2);

    Expression<Func<int, int, int>> sumExpression 
        = Expression.Lambda<Func<int, int, int>>(sumBody, param1, param2);

    return sumExpression;
}

We first create a parameter expression for each lambda expression parameter using Expression.Parameter() method.

Next, we create a binary expression using BinaryExpression and applying the Expression.Add() method because the + operator represents the addition operation.

Finally, we create a lambda expression that combines parameter and binary expressions using the Expression.Lambda() method. In our case, the lambda expression takes two integer parameters and returns their sum.

Let’s discuss compiling and executing expression trees next.

Compiling and Executing Expression Trees

We cannot directly execute an expression tree like a regular method or delegate because an expression tree object only holds the data. Using this data, we can compile it as an executable delegate.  

We need to call the Compile() method from the Expression<TDelegate> object to compile and execute an expression tree. This method returns a delegate of the type TDelegate which we can invoke with the desired arguments.

Let’s create a new method that calls the Compile() method:

public static Func<int, int, int> CompileExpressionTreeToLambdaExpression
(Expression<Func<int, int, int>> expressionTree)
{
    var delegateRepresentingLambdaExpression = expressionTree.Compile();

    return delegateRepresentingLambdaExpression;
}

Let’s compile the sumExpressionTree that we built earlier and execute it:

var delegateSumExpressionTree = ExpressionTrees.CompileExpressionTreeToLambdaExpression(sumExpressionTree)(1, 2);
Console.WriteLine(delegateSumExpressionTree); // prints 3

The Compile() method generates IL code for the expression tree and creates a delegate. The runtime JITs this IL code into machine code like the rest of the program. This process can be expensive, so we should avoid compiling the same expression tree multiple times. We can also use caching techniques to store and reuse compiled delegates.

Expression Tree Explained

To understand more about expression trees, let’s print the values of some of the properties of the sumExpressionTree object and see what value each property holds:

Console.WriteLine(sumExpressionTree.NodeType); // prints "Lambda"
Console.WriteLine(sumExpressionTree.Parameters[0].Name); // prints "number1"
Console.WriteLine(sumExpressionTree.Parameters[1].Name); // prints "number2"
Console.WriteLine(sumExpressionTree.Body.ToString()); // prints "(number1 + number2)"
Console.WriteLine(sumExpressionTree.ReturnType); // prints "System.Int32"

From the output, we can see that the sumExpressionTree.NodeType enum holds the Lambda value. This is the root node of our expression tree. The sumExpressionTree.Parameters property has two items with the values for their Name property as number1 and number2 respectively.

The value of  sumExpressionTree.Body property as (number1 + number2) and the sumExpressionTree.ReturnType as System.Int32.

This pretty much expresses the whole lambda expression as data.

Let’s look into the sumExpressionTree.Body child node and print the NodeType enum property:

Console.WriteLine(sumExpressionTree.Body.NodeType); // prints "Add"

The Add enum value in the sumExpressionTree.Body.NodeType states that we are doing an additive operation.

That said, we can assign sumExpressionTree.Body to a body variable:

BinaryExpression body = (BinaryExpression)sumExpressionTree.Body;

While assigning, we cast to BinaryExpression type to traverse further into the tree. We chose BinaryExpression type because of the additive operation that we are performing. In fact, BinaryExpression is the derived type of Expression base class.

Once we do this, we get additional properties like Left¬†and Right to express the binary additive operation. Let’s print the NodeType of body.Left property and see what value it holds for us:

Console.WriteLine(body.Left.NodeType); // prints "Parameter"

The value of body.Left.NodeType property is Parameter enum value.

Additionally, we can assign body.Left to a leftParam variable:

ParameterExpression leftParam = (ParameterExpression)body.Left;

While assigning, we cast to ParameterExpression as the NodeType is Parameter enum value.

Now we get additional properties in leftParam object:

Console.WriteLine(leftParam.Name); // prints "number1"

From the printed output, the value of leftParam.Name is number1. This represents the parameter name we used as the left parameter to declare the lambda expression.

It’s important to note that both the leftParam and sumExpressionTree.Parameters[0] hold the same reference as they are essentially the same parameter.

Similarly, we can inspect the body.Right node and have similar inferences as body.Left:

Console.WriteLine(body.Right.NodeType); // prints "Parameter"
ParameterExpression rightParam = (ParameterExpression)body.Right;

Console.WriteLine(rightParam.Name); // prints "number2"

If we can picture this tree traversal, we find it to be a tree-like structure and hence the name Expression Tree:

Picture showing the tree like data structure of Expression Trees in C#. There's a root node of type 'Lambda'. There is one child node of type 'Add' to root node. There are two child nodes of type 'Param' to 'Add' node.

The diagram shows that the entire lambda expression is the root node. The root node contains Parameters and Body as its child nodes. The Body node then has Left and Right parameter nodes as its child nodes, completing the tree structure. The dashed lines suggest that the Left and the Right nodes hold the same object references as the Parameters node of the root node.

Applications of Expression Trees

We often use Expression Trees in scenarios such as LINQ queries, dynamic programming, and mocking frameworks. Let’s explore some of these applications one by one.

Entity Framework

The first thing that comes to mind is Entity Framework when discussing Expression Trees. Entity Framework uses expression trees to convert lambda expressions into raw SQL queries. We can write queries in C# that can execute on a database server without writing raw SQL strings.

Before we continue with the query, we have to prepare EF Core for our project. As this article is not related to EF Core preparation, we’ve created all the necessary files that you can find in our source code.

That said, let’s open the EntityFrameworkSqlite class and inspect a simple Entity Framework LINQ Query that filters the list of users by age:

_dbContext
    .Users
    .Where(x => x.Age > 20)
    .ToList();

This query is an expression tree that represents the lambda expression x => x.Age > 20. The ORM can inspect this expression tree and translate it to a SQL query at runtime:

SELECT "u"."Id", "u"."Age", "u"."Name"
FROM "Users" AS "u"
WHERE "u"."Age" > 20

This way, we can write queries in a type-safe and readable way and let the ORM handle the database communication.

Let’s see how it works.

To proceed, we need to know the signature of the Where() method:

IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

The Where() is the extension method of the IQueryable<TSource> interface.

The¬† second parameter’s type is Expression<Func<TSource, bool>> which will convert the regular lambda expression into an expression tree object. Thus, the Entity Framework database providers can inspect this expression tree object at runtime and convert it to raw SQL queries.

Dynamic Methods

Another application of expression trees is creating dynamic methods we can compile and execute at runtime. This can be useful when generating code based on some input or configuration.

For instance, we can dynamically interpret the math statements provided by the users and convert them to an executable code:

public Func<int, int, int> GenerateExecutableFunctionFromMathStatement(string mathStatement)
{
    // Parse the math statement
    var parts = mathStatement.Split('=');

    var variables = parts[1].Split('*');

    var left = Expression.Parameter(typeof(int), variables[0]);
    var right = Expression.Parameter(typeof(int), variables[1]);

    // Create the expression tree
    var add = Expression.Multiply(left, right);
    var lambda = Expression.Lambda(add, left, right);

    // Return the compiled method
    return (Func<int, int, int>)lambda.Compile();
}

This method accepts the math statement as a parameter, parses it, and converts it to an expression tree. Finally, it returns the executable function by calling the Compile() method. We need to call this method to get the function with the math statement =a*b:

var productFunc = GenerateExecutableFunctionFromMathStatement("=a*b");

var product = productFunc(2, 5);

Assert.Equal(10, product);

Please note that this is a simple demonstration to accept only one math statement to do a product of two numbers. But if we get the idea, much more complex scenarios are possible.

Mocking

The other popular application of expression trees is to create mock objects for unit testing. A mock object is a fake object that mimics the behavior of a real object but allows us to control its output and verify its interactions.

To demonstrate, let’s create an ICalculator interface containing one method Add() that takes two numbers as input parameters:

public interface ICalculator
{
    int Add(int number1, int number2);
}

Next, we need the Calculator class that inherits from this interface:

public class Calculator : ICalculator
{
    public int Add(int number1, int number2) => number1 + number2;
}

Then, let’s create a CalculatorService class that takes ICalculator as a dependency:

public class CalculatorService
{
    private readonly ICalculator _calculator;

    public CalculatorService(ICalculator calculator)
    {
        _calculator = calculator;
    }

    public int AddTwoNumbers(int number1, int number2)
    {
        return _calculator.Add(number1, number2);
    }
}

The AddTwoNumbers() method will call the Add() method of the instance of ICalculator.

In this case, we can use a mocking framework such as Moq to create a mock object for an interface:

var calculatorMock = new Mock<ICalculator>();
calculatorMock.Setup(c => c.Add(1, 2)).Returns(3);

Here, we create a mock object that implements the ICalculator interface and sets up an expectation that when the Add() method is called by passing in the numbers 1 and 2 as the arguments, and it should return a total number of 3.

We can use this mock object in our unit tests to verify the logic of our code that depends on the ICalculator interface:

var calculatorService = new CalculatorService(calculatorMock.Object);
Assert.AreEqual(3, calculatorService.AddTwoNumbers(1, 2));

The assertion will pass because the AddTwoNumbers() method within the CalculatorService class calls the mocked Add() method with the same arguments and returns the sum that equals 3.

The mocking frameworks use expression trees to capture and return the arguments and values of the methods we want to mock. They also use the expression trees to verify that we call the mock object with the expected arguments and in the expected order.

These are just some of the applications of expression trees in C#. There are other advanced applications like dynamic language runtime (DLR) to provide interoperability between .NET and dynamic languages like JavaScript, PHP, Ruby, and Python.

Limitations of Expression Trees

Any powerful feature is not without its limitations. Expression trees can’t use some of these features:

  • async, await in the expression trees
  • Statements or statement-bodied lambda expressions (multi-line lambda)
  • Interpolated strings
  • Throw exceptions
  • Multi-dimensional array initialization
  • and more

We can see the complete list of limitations here.

Performance Considerations with Expression Trees

One of the main performance issues with expression trees is the overhead of creating and compiling them. Creating an expression tree involves allocating memory for the tree nodes and setting their properties. Compiling an expression tree involves generating IL code and emitting it to a dynamic assembly. Both of these operations are relatively expensive and can affect the performance of our application if we do them frequently or in a tight loop.

To reduce the overhead of creating and compiling expression trees, we can follow these guidelines:

  • Cache the expression trees and their compiled delegates whenever possible
  • The bigger the tree grows, the higher the compute and memory¬†required to compile
  • Use expression trees only when we need their flexibility and power

Conclusion

In this article, we have seen how to create and use expression trees in different scenarios. We have also learned its different applications. Finally, we also looked into its limitations and performance considerations. You can find more articles in C# Intermediate to learn intermediate and advanced topics in C#.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!