C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
213: Expression Trees Basics
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)
- MemberExpression (Accessing
- ParameterExpression (The
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:
- Define a simple
Productclass with propertiesName(string) andPrice(decimal). - Create a method
public static void InspectExpression(Expression<Func<Product, bool>> expression). - Inside that method, cast the
expression.Bodyto aBinaryExpression. - From that
BinaryExpression, find theMemberExpression(the left side of the comparison). - Print the
Member.Nameof that expression to the console.
// This should print "Price" to the console
InspectExpression(p => p.Price > 100);
// This should print "Name" to the console
InspectExpression(p => p.Name == "Laptop");
There are no comments for now.