Expression Trees

Vaibhav • September 11, 2025

In the previous article, we explored lambda expressions - a concise way to define inline functions using the => operator. Lambda expressions are not just syntactic sugar; they also unlock deeper capabilities in C#, especially when used as expression trees. Expression trees allow you to represent code as data - enabling powerful scenarios like dynamic query generation, runtime analysis, and building interpreters.

In this article, we’ll explore what expression trees are, how they differ from regular delegates, how to construct and inspect them, and why they’re essential for technologies like LINQ to SQL and Entity Framework. We’ll build on your understanding of delegates and lambdas, and introduce the System.Linq.Expressions namespace - the foundation for working with expression trees.

What is an Expression Tree?

An expression tree is a data structure that represents code in a tree-like format. Instead of compiling a lambda into executable code, C# can compile it into an object graph - where each node represents a part of the expression (like a method call, a constant, or a binary operation).

This allows you to inspect, modify, and even generate code at runtime. Expression trees are used extensively in LINQ providers that translate C# code into SQL, JavaScript, or other languages.

Expression trees are built using types from the System.Linq.Expressions namespace. The core type is Expression<TDelegate>, which wraps a lambda expression as a tree.

Declaring an Expression Tree

To declare an expression tree, you use the same lambda syntax - but assign it to an Expression<TDelegate> instead of a regular delegate. Let’s look at a simple example:

using System;
using System.Linq.Expressions;

Expression<Func<int, int>> square = x => x * x;

This creates an expression tree that represents the function x => x * x. Unlike a regular Func<int, int>, this is not executable code - it’s a data structure that describes the code.

Inspecting an Expression Tree

You can inspect the structure of an expression tree by accessing its properties. Each node in the tree is an instance of a subclass of Expression, such as BinaryExpression, ParameterExpression, or ConstantExpression.

Console.WriteLine(square.Body); // Output: (x * x)
Console.WriteLine(square.Parameters[0].Name); // Output: x

The Body property contains the main expression (in this case, a multiplication). The Parameters property contains the input variables. You can walk the tree to understand how the expression is constructed.

Expression Tree vs Delegate

A regular delegate compiles to executable code. An expression tree compiles to a data structure. This distinction is crucial:

Func<int, int> squareFunc = x => x * x;
Expression<Func<int, int>> squareExpr = x => x * x;

The first line creates a delegate that can be invoked directly. The second creates an expression tree that can be analyzed or transformed. You cannot invoke squareExpr directly - you must compile it first.

Note: To execute an expression tree, you must compile it using Compile(). This converts the tree into a delegate.

var compiled = squareExpr.Compile();
Console.WriteLine(compiled(5)); // Output: 25

Building Expression Trees Manually

You can also build expression trees manually using factory methods like Expression.Add, Expression.Parameter, and Expression.Lambda. This gives you full control over the structure.

ParameterExpression x = Expression.Parameter(typeof(int), "x");
BinaryExpression body = Expression.Multiply(x, x);
Expression<Func<int, int>> square = Expression.Lambda<Func<int, int>>(body, x);

This builds the same expression tree as before - but manually. You define the parameter, the operation, and then wrap it in a lambda. This approach is useful when generating code dynamically.

Expression Trees in LINQ

LINQ providers like Entity Framework use expression trees to translate C# queries into SQL. When you write a query like:

var result = db.Users.Where(u => u.Age > 18);

The lambda u => u.Age > 18 is captured as an expression tree. The provider walks the tree and generates a SQL query like SELECT * FROM Users WHERE Age > 18. This is only possible because the code is represented as data.

LINQ to Objects uses delegates, while LINQ to SQL uses expression trees. This is why some LINQ methods accept Func<T, bool> and others accept Expression<Func<T, bool>>.

Modifying Expression Trees

You can modify expression trees by writing visitors - classes that walk the tree and replace nodes. This allows you to rewrite code dynamically, inject logging, or optimize expressions.

The base class for this is ExpressionVisitor. You override methods like VisitBinary or VisitParameter to customize behavior.

class MultiplyToAddVisitor : ExpressionVisitor
{
    protected override Expression VisitBinary(BinaryExpression node)
    {
        if (node.NodeType == ExpressionType.Multiply)
        {
            return Expression.Add(node.Left, node.Right);
        }
        return base.VisitBinary(node);
    }
}

This visitor replaces multiplication with addition. You can apply it to an expression tree and then compile the result.

var visitor = new MultiplyToAddVisitor();
var modified = visitor.Visit(squareExpr);
var newLambda = Expression.Lambda<Func<int, int>>(modified, squareExpr.Parameters);
Console.WriteLine(newLambda.Compile()(5)); // Output: 10

The original expression x * x becomes x + x. This shows how expression trees enable dynamic code transformation.

Limitations of Expression Trees

Expression trees can only represent a subset of C# - primarily expressions, not statements. You cannot include loops, conditionals, or local variables. Also, not all .NET types are supported in expression trees.

For example, you cannot represent if statements or try-catch blocks. Expression trees are designed for pure, side-effect-free logic - ideal for queries and transformations.

Note: If you need full control over code generation, consider using Roslyn - the C# compiler API. Expression trees are simpler and safer, but limited in scope.

Expression Trees and Functional Programming

Expression trees align well with functional programming principles. They treat code as data, support pure functions, and enable composition. You can build higher-order functions that generate or transform expression trees - creating reusable logic pipelines.

For example, you might write a function that returns a filter expression based on user input:

Expression<Func<User, bool>> MakeFilter(int minAge)
{
    return u => u.Age > minAge;
}

This function returns an expression tree that can be used in a LINQ query. It’s composable, testable, and declarative - all hallmarks of functional design.

Summary

Expression trees are a powerful feature in C# that allow you to represent code as data. They build on delegates and lambda expressions, and enable dynamic query generation, runtime analysis, and code transformation. You learned how to declare expression trees, inspect their structure, compile them into delegates, and modify them using visitors.

Expression trees are essential for technologies like LINQ to SQL and Entity Framework. They support functional programming patterns and open the door to advanced scenarios like interpreters, rule engines, and dynamic APIs. In the next article, we’ll explore Action and Func Delegates - the built-in delegate types that make working with lambdas and expression trees even easier.