C# > Functional Programming > Lambdas and Expressions > Expression Trees

Building and Compiling Expression Trees for Dynamic Filtering

This snippet demonstrates how to build an expression tree to dynamically filter a collection based on runtime criteria. Expression trees are powerful tools for creating dynamic queries and can be used in scenarios where the filtering logic is not known at compile time.

Concepts Behind the Snippet

Expression trees represent code in a tree-like data structure. Each node in the tree represents an expression, such as a method call or a binary operation. The Expression class in the System.Linq.Expressions namespace provides the API for building these trees. The key steps are: 1. **Define Parameters:** Create ParameterExpression objects to represent the input parameters to the lambda expression. These parameters are what the expression will operate on. 2. **Build the Body:** Construct the body of the expression using methods like Expression.Property, Expression.Equal, and Expression.AndAlso. These methods allow you to create nodes in the expression tree that represent operations on the parameters. 3. **Create the Lambda Expression:** Use Expression.Lambda to create a lambda expression from the parameter(s) and the body of the expression. This represents the complete expression tree. 4. **Compile the Expression:** Use Compile() to turn the expression tree into a delegate that can be executed. This allows you to use the dynamically built expression in your code.

Code Example: Dynamic Filtering with Expression Trees

The code defines a Product class with Name and Price properties. The BuildFilter method dynamically creates a filter based on a property name and value. It builds an expression tree that represents the filter logic and then compiles it into a Func delegate. This delegate can then be used with the Where method to filter a list of products. The Main method demonstrates how to use the BuildFilter method to filter products by name and price.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Example
{
    public static Func<Product, bool> BuildFilter(string propertyName, object propertyValue)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(Product), "p");

        // p.PropertyName
        MemberExpression property = Expression.Property(parameter, propertyName);

        // Convert the value to the property type
        ConstantExpression constant = Expression.Constant(propertyValue, property.Type);

        // p.PropertyName == propertyValue
        BinaryExpression equality = Expression.Equal(property, Expression.Convert(constant, property.Type));

        // p => p.PropertyName == propertyValue
        Expression<Func<Product, bool>> lambda = Expression.Lambda<Func<Product, bool>>(equality, parameter);

        return lambda.Compile();
    }

    public static void Main(string[] args)
    {
        List<Product> products = new List<Product>
        {
            new Product { Name = "Laptop", Price = 1200.00m },
            new Product { Name = "Mouse", Price = 25.00m },
            new Product { Name = "Keyboard", Price = 75.00m }
        };

        // Dynamically create a filter to find products with the name "Laptop"
        Func<Product, bool> nameFilter = BuildFilter("Name", "Laptop");
        List<Product> filteredProductsByName = products.Where(nameFilter).ToList();

        Console.WriteLine("Products filtered by name:");
        foreach (var product in filteredProductsByName)
        {
            Console.WriteLine($"Name: {product.Name}, Price: {product.Price}");
        }

        // Dynamically create a filter to find products with a price of 25.00
        Func<Product, bool> priceFilter = BuildFilter("Price", 25.00m);
        List<Product> filteredProductsByPrice = products.Where(priceFilter).ToList();

        Console.WriteLine("\nProducts filtered by price:");
        foreach (var product in filteredProductsByPrice)
        {
            Console.WriteLine($"Name: {product.Name}, Price: {product.Price}");
        }
    }
}

Real-Life Use Case

Expression trees are highly beneficial in building dynamic query systems. Consider a scenario where you have a search form with multiple filter criteria. Instead of writing separate queries for each possible combination of filters, you can dynamically construct an expression tree based on the user's input. This leads to more maintainable and efficient code.

Best Practices

  • Cache Compiled Expressions: Compiling an expression tree is a relatively expensive operation. If you are using the same expression tree multiple times, cache the compiled delegate to improve performance.
  • Handle Exceptions Carefully: When building expression trees dynamically, it's important to handle potential exceptions, such as invalid property names or type mismatches.
  • Use ExpressionVisitor to Modify Expressions: If you need to modify an existing expression tree, use the ExpressionVisitor class to traverse and modify the tree in a structured way.

Interview Tip

Be prepared to explain the difference between lambda expressions and expression trees. Lambda expressions are a concise way to represent anonymous functions, while expression trees represent code as data. Expression trees can be compiled into delegates, allowing for dynamic code generation.

When to Use Them

Use expression trees when you need to:

  • Dynamically construct queries at runtime.
  • Serialize code for execution in a different context (e.g., a database server).
  • Analyze and manipulate code programmatically.

Memory Footprint

Expression trees can have a larger memory footprint compared to compiled code because they store the code's structure as data. However, the benefits of dynamic query generation often outweigh the increased memory usage. Caching compiled expressions helps to mitigate the performance impact.

Alternatives

  • Dynamic LINQ: The Dynamic LINQ library provides a string-based syntax for building LINQ queries at runtime. This can be simpler to use than expression trees for basic scenarios.
  • Predicate Builder: The Predicate Builder pattern allows you to build complex predicates by composing simpler predicates. This is a more readable and maintainable approach for complex filtering logic.

Pros

  • Flexibility: Expression trees allow you to create highly flexible and dynamic queries.
  • Compile-Time Safety: Expression trees are type-safe, which helps to prevent runtime errors.
  • Serialization: Expression trees can be serialized and transmitted to other systems for execution.

Cons

  • Complexity: Building expression trees can be more complex than writing regular LINQ queries.
  • Performance Overhead: Compiling expression trees can introduce a performance overhead.
  • Debugging: Debugging expression trees can be more challenging than debugging regular code.

FAQ

  • What is the difference between a lambda expression and an expression tree?

    A lambda expression is a short way of writing an anonymous method. An expression tree represents code as a data structure (a tree). An expression tree can be compiled into a delegate (which is like a lambda expression) or it can be analyzed and transformed.
  • Why would I use expression trees instead of just writing regular C# code?

    Expression trees allow you to dynamically construct code at runtime. This is useful when you need to create queries or filters based on user input or other runtime conditions. You can also serialize an expression tree and send it to another application, such as a database server, to be executed there.