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