Skip to Content
Course content

213: Expression Trees Basics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've been using LINQ, you've likely seen Expression<Func<T, bool>> popping up in method signatures. At first glance, it looks almost identical to a standard Func<T, bool>. This leads to the most common stumble I see when developers first hit this topic: the belief that an Expression is just a fancy way to pass a delegate around.

"It's just a delegate with a different name"

It's tempting to think that Expression<Func<int, bool>> is just a wrapper for a piece of executable code. But here is the cold, hard truth: a delegate is a pointer to a method that the CPU can execute immediately. An Expression Tree is data. It is a structural description of what the code would do if it were ever compiled.

Let me show you why this distinction matters. Try this in your head (or your IDE):

Func<int, bool> isPositiveFunc = x => x > 0;
bool result1 = isPositiveFunc(10); // Works perfectly.

Expression<Func<int, bool>> isPositiveExpr = x => x > 0;
// bool result2 = isPositiveExpr(10); // ERROR: Cannot invoke an Expression.

The second line fails because isPositiveExpr isn't a function; it's an object. Specifically, it's an Expression object that contains a tree of other objects. If you were to inspect isPositiveExpr in a debugger, you wouldn't see a memory address to a method; you'd see a LambdaExpression containing a BinaryExpression (the "greater than" part), which in turn has a ParameterExpression (the "x") and a ConstantExpression (the "0").

Code as Data: The Anatomy of an Expression Tree

Why on earth would we want our code to be represented as a data structure instead of just running it? Because once code is data, you can rewrite it or translate it.

This is the "secret sauce" behind Entity Framework. When you write db.Products.Where(p => p.Price > 10), EF doesn't actually execute that C# lambda against your database. If it did, it would have to pull every single product from your SQL server into your RAM just to check the price. Instead, EF accepts an Expression, walks through the tree, sees the BinaryExpression for "greater than," and translates that specific tree node into the string "WHERE Price > 10" to send to SQL Server.

If you actually want to execute an expression tree in C#, you have to tell the runtime to compile that data back into actual IL (Intermediate Language) first:

Expression<Func<int, bool>> expr = x => x > 0;
var compiled = expr.Compile(); // This turns the data tree back into a delegate
bool result = compiled(10); // Now it works.

I'll be honest: .Compile() is expensive. You shouldn't do it inside a tight loop. But for the purpose of understanding the basics, it's the bridge between "code as a description" and "code as an action."

Exploring the Tree Structure

To really wrap your head around this, you need to stop thinking about the => symbol as a function and start thinking of it as a map. In a basic expression like p => p.Age > 18, the tree looks like this:

  • LambdaExpression (The root)
    • ParameterExpression (The p)
    • BinaryExpression (The > operator)
      • MemberExpression (Accessing p.Age)
      • ConstantExpression (The value 18)

By treating logic as a tree, we can write software that analyzes other software. It's a powerful pattern, and while it feels academic at first, it's the foundation of almost every modern ORM and mapping library in the .NET ecosystem.




📋 Practical Task

Building a Dynamic Product Property Inspector

Your goal is to create a small utility that takes an Expression<Func<Product, bool>> and prints out the name of the property being accessed in the expression. This simulates how a library might "discover" which database column to filter by.

Requirements:

  1. Define a simple Product class with properties Name (string) and Price (decimal).
  2. Create a method public static void InspectExpression(Expression<Func<Product, bool>> expression).
  3. Inside that method, cast the expression.Body to a BinaryExpression.
  4. From that BinaryExpression, find the MemberExpression (the left side of the comparison).
  5. Print the Member.Name of that expression to the console.
Example Test Case:
// This should print "Price" to the console
InspectExpression(p => p.Price > 100);

// This should print "Name" to the console
InspectExpression(p => p.Name == "Laptop");
Rating
0 0

There are no comments for now.

to be the first to leave a comment.