Expression trees in C# allow you to represent code as data. Instead of executing a method directly, you can inspect its structure at runtime. This is widely used in LINQ providers, ORMs, and background job systems.
This article explains expression trees using a practical method pattern commonly seen in background processing systems.
Assume we want to schedule a method call for later execution. Instead of calling the method directly, we pass it as an Expression<Action>:
public class FireAndForgetJobs
{
private Expression<Action> _expression;
public FireAndForgetJobs(Expression<Action> expression)
{
_expression = expression;
}
public async Task ExecuteAsync(CancellationToken token)
{
MethodCallExpression? methodCall =
(MethodCallExpression)_expression.Body;
string functionName = methodCall.Method.Name;
Type typeName = methodCall.Method.DeclaringType;
// Logic here to invoke method dynamically
}
}
Now let's break down what is happening.
What Is an Expression Tree?
An expression tree is an object representation of code.
Normally, when you write:
DoSomething();
The method executes immediately.
But when you write:
Expression<Action> expr = () => DoSomething();
You are not executing the method.
You are creating a tree structure that represents:
- The method being called
- The declaring type
- The arguments (if any)
The compiler builds a data structure instead of compiled instructions.
This structure lives in:
System.Linq.Expressions
Why Use Expression Trees?
Expression trees allow you to:
- Inspect the method name
- Extract parameter values
- Identify the declaring type
- Serialize invocation metadata
- Reconstruct and execute the call later
This is how libraries like Hangfire capture background job definitions.
Understanding the Code Step-by-Step
1. Capturing the Expression
Expression<Action> expression
This means:
- No return value
- Represents a method call
- Stored as a tree
If someone passes:
new FireAndForgetJobs(() => SendEmail("admin@test.com"));
Nothing executes yet.
2. Accessing the Body
_expression.Body
The body of this lambda is a MethodCallExpression.
So we cast:
(MethodCallExpression)_expression.Body
Now we can access:
methodCall.Method.NamemethodCall.Method.DeclaringTypemethodCall.Arguments
3. Extracting Metadata
string functionName = methodCall.Method.Name;
Type typeName = methodCall.Method.DeclaringType;
This gives:
- The exact method name
- The class it belongs to
You now have enough information to:
- Store this in a database
- Serialize it
- Invoke it using reflection
What the Expression Tree Actually Contains
For this lambda:
() => SendEmail("admin@test.com")
The tree roughly looks like:
LambdaExpression
└── MethodCallExpression
├── MethodInfo
└── Arguments
└── ConstantExpression ("admin@test.com")
It is a structured object graph.
Why Not Use Delegates?
A delegate (Action) executes directly.
You cannot inspect:
- Method name
- Parameters
- Target type
With Expression<Action>, you can inspect everything before execution.
This makes it ideal for:
- ORMs (e.g., translating LINQ to SQL)
- Job schedulers
- Rule engines
- Dynamic query builders
Limitations
Expression trees:
- Cannot represent all C# syntax
- Have performance overhead during inspection
- Require careful casting (incorrect casts cause runtime errors)
They are powerful but should be used intentionally.
When to Use Expression Trees
Use them when you need:
- Code analysis at runtime
- Metadata extraction
- Dynamic query translation
- Delayed execution with introspection
Do not use them for simple method invocation.
Conclusion
Expression trees convert executable code into a structured data model. In the example shown:
- The method call is captured.
- Its metadata is extracted.
- It can be stored and executed later.
This pattern underpins many advanced .NET systems and enables flexible runtime behavior without hardcoding execution paths.
If you are building background processing, dynamic execution engines, or LINQ providers, expression trees are a fundamental tool.
Top comments (0)