0006 Expression Trees in C#

Expression trees are a powerful feature in C# that allow you to represent code as data. They are commonly used in frameworks like Entity Framework to translate LINQ queries into SQL or other query languages.

What are Expression Trees?

Example: Creating and Compiling an Expression Tree

Let’s create a simple expression tree that represents a lambda function x => x * 2:

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        // Define the parameter (x)
        ParameterExpression parameter = Expression.Parameter(typeof(int), "x");

        // Define the expression body (x * 2)
        BinaryExpression body = Expression.Multiply(parameter, Expression.Constant(2));

        // Create the lambda expression (x => x * 2)
        Expression<Func<int, int>> lambda = Expression.Lambda<Func<int, int>>(body, parameter);

        // Compile the expression into a delegate
        Func<int, int> compiledLambda = lambda.Compile();

        // Execute the compiled lambda
        int result = compiledLambda(5); // Output: 10
        Console.WriteLine(result);
    }
}

Key Points:

  1. Building Blocks:

  2. Compilation:

  3. Use Cases:

Advanced Example: Dynamic Filtering with Entity Framework

You can use expression trees to build dynamic LINQ queries. For example, filtering a list based on a property name and value:

public static IQueryable<T> ApplyFilter<T>(IQueryable<T> query, string propertyName, object value)
{
    // Define the parameter (x)
    var parameter = Expression.Parameter(typeof(T), "x");

    // Access the property (x.PropertyName)
    var property = Expression.Property(parameter, propertyName);

    // Create the equality expression (x.PropertyName == value)
    var equality = Expression.Equal(property, Expression.Constant(value));

    // Create the lambda expression (x => x.PropertyName == value)
    var lambda = Expression.Lambda<Func<T, bool>>(equality, parameter);

    // Apply the filter
    return query.Where(lambda);
}

Gotchas:


Why Use Expression Trees?

  1. Dynamic Query Generation:

  2. LINQ-to-SQL or LINQ-to-Entities:

  3. Runtime Code Generation:

  4. Performance Optimization:


Practical Use Cases

1. Dynamic Filtering in Entity Framework

Imagine you’re building a search feature where users can filter data by any column. Without expression trees, you’d have to write a lot of repetitive code. With expression trees, you can dynamically build the query:

public static IQueryable<T> ApplyFilter<T>(IQueryable<T> query, string propertyName, object value)
{
    var parameter = Expression.Parameter(typeof(T), "x");
    var property = Expression.Property(parameter, propertyName);
    var equality = Expression.Equal(property, Expression.Constant(value));
    var lambda = Expression.Lambda<Func<T, bool>>(equality, parameter);
    return query.Where(lambda);
}

// Usage
var filteredUsers = ApplyFilter(dbContext.Users, "Age", 30).ToList();

This avoids writing separate methods for each filter (e.g., FilterByAge, FilterByName).

2. Building a Rules Engine

If you’re building a system where rules are defined at runtime (e.g., discount rules for an e-commerce site), expression trees can help you evaluate those rules dynamically:

public static Func<T, bool> CompileRule<T>(string propertyName, object value)
{
    var parameter = Expression.Parameter(typeof(T), "x");
    var property = Expression.Property(parameter, propertyName);
    var equality = Expression.Equal(property, Expression.Constant(value));
    var lambda = Expression.Lambda<Func<T, bool>>(equality, parameter);
    return lambda.Compile();
}

// Usage
var rule = CompileRule<User>("Age", 30);
bool isMatch = rule(new User { Age = 30 }); // Returns true

3. LINQ-to-SQL Translation

When you write a LINQ query like this:

var users = dbContext.Users.Where(u => u.Age > 30).ToList();

Entity Framework uses expression trees to convert the lambda u => u.Age > 30 into SQL:

SELECT * FROM Users WHERE Age > 30

Without expression trees, this translation wouldn’t be possible.

4. Performance Optimization

If you’re using reflection to access properties repeatedly, it can be slow. Expression trees can compile a delegate for property access, which is much faster:

public static Func<T, object> CreatePropertyGetter<T>(string propertyName)
{
    var parameter = Expression.Parameter(typeof(T), "x");
    var property = Expression.Property(parameter, propertyName);
    var convert = Expression.Convert(property, typeof(object));
    var lambda = Expression.Lambda<Func<T, object>>(convert, parameter);
    return lambda.Compile();
}

// Usage
var getter = CreatePropertyGetter<User>("Name");
var name = getter(new User { Name = "Alice" }); // Returns "Alice"

This is faster than using PropertyInfo.GetValue in a loop.


When Not to Use Expression Trees