Courses/C#/Expression Trees

    Expression Trees: Building Dynamic Logic at Runtime

    Lesson 18 โ€ข Advanced Track

    What You'll Learn

    • Understand the difference between compiled lambdas (Func) and expression trees (Expression<Func>)
    • Build expression trees manually using the Expression API
    • Compile expression trees into executable code at runtime
    • Create dynamic query filters by composing expressions programmatically
    • Use ExpressionVisitor to inspect and transform expression trees
    • Apply expression trees to real-world scenarios like dynamic filtering

    ๐Ÿ’ก Real-World Analogy

    A regular lambda is like a baked cake โ€” ready to eat but you can't change the recipe. An expression tree is like the recipe card itself โ€” you can read it, modify the ingredients, translate it into a different cuisine, or bake it into a cake whenever you're ready. Entity Framework uses expression trees to read your C# LINQ queries and translate them into SQL โ€” it needs the recipe, not the cake.

    1. Expression Trees vs Compiled Lambdas

    Func<int, int, int> is compiled IL code โ€” you can execute it but can't inspect its structure. Expression<Func<int, int, int>> represents the same logic as a data structure you can traverse, modify, and compile on demand. This is how EF Core translates LINQ to SQL.

    Expression Trees Basics

    Compare Func vs Expression and build trees manually.

    Try it Yourself ยป
    C#
    using System;
    using System.Linq.Expressions;
    
    class Program
    {
        static void Main()
        {
            // A lambda compiled to IL (executable code)
            Func<int, int, int> addFunc = (a, b) => a + b;
            Console.WriteLine($"Func result: {addFunc(3, 5)}");
    
            // The SAME lambda as an expression tree (data structure)
            Expression<Func<int, int, int>> addExpr = (a, b) => a + b;
    
            // Inspect the tree structure
            Console.WriteLine($"\nExpression type: {addExpr.NodeType}");
    ...

    2. Dynamic Query Building

    The real power of expression trees is dynamic composition. Build filter expressions conditionally based on user input โ€” only add category, price range, or search filters when the user specifies them. This is how search pages with optional filters work.

    Dynamic Filter Builder

    Build conditional query filters using expression composition.

    Try it Yourself ยป
    C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    
    class Product
    {
        public string Name { get; set; } = "";
        public decimal Price { get; set; }
        public string Category { get; set; } = "";
    }
    
    class Program
    {
        // Build a dynamic filter expression
        static Expression<Func<Product, bool>> BuildFilter(
            string? category = null,
            decimal? minPrice = null,
            decimal? maxPrice = null)
        {
            var param = Expression.Pa
    ...

    3. ExpressionVisitor โ€” Transform Trees

    ExpressionVisitor walks an expression tree and lets you modify nodes. Override methods like VisitBinary to replace, remove, or add nodes. This pattern is used by ORMs to optimise queries and by mocking frameworks to set up expectations.

    Expression Visitor

    Transform expression trees by replacing operations.

    Try it Yourself ยป
    C#
    using System;
    using System.Linq.Expressions;
    
    // Custom visitor that replaces addition with multiplication
    class AddToMultiplyVisitor : ExpressionVisitor
    {
        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (node.NodeType == ExpressionType.Add)
            {
                Console.WriteLine($"  Replacing {node} with Multiply");
                return Expression.Multiply(
                    Visit(node.Left),
                    Visit(node.Right));
            }
            return base.Vi
    ...

    Pro Tips

    • ๐Ÿ’ก EF Core needs Expression, not Func: dbContext.Products.Where(expr) translates to SQL. If you pass a Func, EF downloads all rows and filters in memory.
    • ๐Ÿ’ก Cache compiled expressions: .Compile() is expensive. If you call the same expression repeatedly, compile once and reuse the Func.
    • ๐Ÿ’ก Use LINQKit for easier composition: The PredicateBuilder library makes combining Expression predicates with AND/OR much simpler.
    • ๐Ÿ’ก Expression trees are immutable: You can't modify a tree โ€” you create a new one. ExpressionVisitor returns a new tree.

    Common Mistakes

    • Using Func where Expression is needed: LINQ to Entities requires Expression<Func<T, bool>>. Using Func<T, bool> causes client-side evaluation or crashes.
    • Forgetting to compile: Expression trees are data, not code. Call .Compile() to get an executable delegate.
    • Complex trees in hot paths: Building and compiling expression trees is slow. Don't do it in tight loops โ€” build once, compile, cache.
    • Not handling null body: When building dynamic filters, ensure the body expression is never null โ€” use Expression.Constant(true) as a fallback.

    ๐ŸŽ‰ Lesson Complete

    • โœ… Expression trees represent code as inspectable data structures
    • โœ… Expression<Func<T>> vs Func<T> โ€” data vs compiled code
    • โœ… Build trees manually with Expression.Parameter, .Multiply, .Lambda
    • โœ… Dynamic query building: compose filters conditionally for search UIs
    • โœ… ExpressionVisitor transforms trees by overriding Visit methods
    • โœ… Next lesson: Reflection & Dynamic Type Inspection

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy Policy โ€ข Terms of Service