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.