Expression trees in C# can seem complex, especially if you’re coming from JavaScript or haven’t worked with low-level runtime concepts. But at their core, expression trees are just a way to treat code as data. Let’s break it down step by step.
1️⃣ Lambda Functions – Tiny Anonymous Functions
In C#, you can define a function without giving it a name using the lambda operator =>:
x => x + 5
- Read it as: “given x, return x + 5”
- This is a lambda function
- Lambdas can be stored in a delegate or turned into an expression tree
Example with a delegate:
Func<int, int> addFive = x => x + 5;
Console.WriteLine(addFive(10)); // 15
Here:
Func<int,int>is a delegate (a variable that can hold a function)addFiveis a variable storing the functionx => x + 5is the lambda
2️⃣ Delegates – Variables Holding Functions
A delegate in C# is essentially a type-safe function pointer:
Func<int,int> myFunc = x => x * 2;
Console.WriteLine(myFunc(3)); // 6
myFuncisn’t the number 6—it’s the function that doubles a number- You can call it with different inputs
Delegates allow passing functions around, storing them, and returning them from other functions, similar to JavaScript.
3️⃣ Expression Trees – Code as Data
When you write:
Expression<Func<int,int>> expr = x => x + 5;
- Instead of immediately creating a runnable function, C# builds a tree of objects representing the code
- Each part of the lambda becomes a node in the tree
Example tree structure:
Expression<Func<int,int>> (root lambda)
└─ Body: BinaryExpression (x + 5)
├─ Left: ParameterExpression (x)
└─ Right: ConstantExpression (5)
- Each node is an object in memory
BinaryExpression= operator (+)ParameterExpression= variable (x)ConstantExpression= literal (5)
This tree lets you inspect, modify, or analyze the code before running it.
3.1 Compiling an Expression Tree
You can convert the tree into a runnable function using .Compile():
var func = expr.Compile();
Console.WriteLine(func(10)); // 15
Under the hood:
- .NET traverses the tree
- Generates IL (Intermediate Language) instructions for each node
- JIT compiler converts IL into machine code
So the tree acts like a blueprint, and .Compile() constructs the real building (runnable code).
4️⃣ What is IL (Intermediate Language)?
- IL is .NET’s middle code between C# and machine code
- Example:
int y = x + 5;
Becomes IL:
ldloc.0 // load x
ldc.i4.5 // load 5
add // add
stloc.1 // store in y
- JIT compiler turns IL into CPU instructions at runtime
- Expression trees initially don’t produce IL, only when
.Compile()is called
5️⃣ JavaScript Functions vs C# Delegates and Expression Trees
In JavaScript, functions are first-class citizens:
const addFive = x => x + 5;
console.log(addFive(10)); // 15
- Functions can be assigned to variables
- Can be returned from other functions:
function makeAdder(y) {
return function(x) {
return x + y;
};
}
const addTen = makeAdder(10);
console.log(addTen(5)); // 15
C# equivalent using delegates:
Func<int,int> MakeAdder(int y) {
return x => x + y;
}
var addTen = MakeAdder(10);
Console.WriteLine(addTen(5)); // 15
Key Similarities:
| Concept | JavaScript | C# |
|---|---|---|
| Variable holding function | const f = x => x+5 | Func<int,int> f = x => x+5 |
| Return a function | return function(x){…} | return x => … |
| Pass function around | f(g) | f(g) |
Differences:
| Feature | JavaScript | C# |
|---|---|---|
| Type system | Dynamic | Strongly typed (Func<>) |
| Inspect/modify code | Hard / not standard | Expression Trees allow it |
| Compile to machine code | JIT via engine (V8, SpiderMonkey) | IL → JIT → machine code |
6️⃣ Why This Matters
Expression trees in C# give you powerful introspection and dynamic behavior:
- Look inside a lambda
- Modify it or generate new functions dynamically
- Generate queries for databases (LINQ)
- Build dynamic functionality at runtime
Meanwhile, JavaScript gives you flexible function handling, but without the built-in “code as data” tree structure.
7️⃣ TL;DR – First Principles Recap
- Lambda (
=>) = tiny anonymous function - Delegate = variable that holds a function
- Expression tree = objects representing code in a tree structure
- IL = intermediate language that gets JIT-compiled
- .Compile() = turns a tree blueprint into runnable code
- JavaScript functions can also be variables or returned functions, but C# expression trees let you inspect and manipulate the function itself
Expression trees are a bridge between code, data, and runtime, letting C# inspect and generate code dynamically.
Leave a Reply