一、表达式树是什么?

想象你要组装一台电脑,但不想提前买好所有配件,而是根据用户需求动态选择CPU、内存等部件。表达式树就像这个"组装说明书",它允许我们在运行时动态构建代码逻辑,而不是在编译时写死。

在C#中,表达式树(Expression Tree)是一种将代码表示为数据结构的方式。比如 x => x > 0 这个Lambda表达式,背后就是一个由ParameterExpressionBinaryExpression等节点组成的树状结构。

// 技术栈:C# .NET 6
using System;
using System.Linq.Expressions;

// 示例1:手动构建 x => x > 0 的表达式树
var parameter = Expression.Parameter(typeof(int), "x");               // 参数节点
var constant = Expression.Constant(0);                                // 常量节点
var comparison = Expression.GreaterThan(parameter, constant);         // 比较节点
var lambda = Expression.Lambda<Func<int, bool>>(comparison, parameter); // 组合成Lambda

// 测试输出
var func = lambda.Compile();
Console.WriteLine(func(5));  // 输出:True

二、动态构建的典型场景

1. 动态查询条件

比如电商平台要根据用户选择的筛选条件(价格区间、品牌等)拼接SQL查询:

// 技术栈:C# .NET 6
// 动态构建 product => product.Price > 100 && product.Stock < 10
var productParam = Expression.Parameter(typeof(Product), "p");
var priceProp = Expression.Property(productParam, "Price");
var stockProp = Expression.Property(productParam, "Stock");

// 构建 Price > 100
var priceCompare = Expression.GreaterThan(priceProp, Expression.Constant(100));
// 构建 Stock < 10
var stockCompare = Expression.LessThan(stockProp, Expression.Constant(10));
// 组合 && 条件
var finalExpr = Expression.AndAlso(priceCompare, stockCompare);

var lambda = Expression.Lambda<Func<Product, bool>>(finalExpr, productParam);

2. 反射的替代方案

相比反射,表达式树的性能更高:

// 技术栈:C# .NET 6
// 动态调用对象的ToString方法
var obj = new Product { Id = 1 };
var param = Expression.Parameter(typeof(object), "x");
var methodCall = Expression.Call(
    Expression.Convert(param, obj.GetType()), // 转换类型
    "ToString", 
    Type.EmptyTypes
);
var lambda = Expression.Lambda<Func<object, string>>(methodCall, param).Compile();

Console.WriteLine(lambda(obj)); // 输出Product的ToString结果

三、高阶技巧与陷阱

1. 处理复杂类型

当属性是嵌套对象时,需要链式访问:

// 技术栈:C# .NET 6
// 构建 order => order.Customer.Address.City == "北京"
var orderParam = Expression.Parameter(typeof(Order), "o");
var customerProp = Expression.Property(orderParam, "Customer");
var addressProp = Expression.Property(customerProp, "Address");
var cityProp = Expression.Property(addressProp, "City");
var cityCompare = Expression.Equal(cityProp, Expression.Constant("北京"));

2. 注意闭包问题

在循环中构建表达式时,要避免变量捕获错误:

// ❌ 错误写法:所有lambda都会捕获最后一个i的值
var filters = new List<Func<int, bool>>();
for (int i = 0; i < 3; i++) {
    filters.Add(x => x == i); 
}

// ✅ 正确做法:使用局部变量中转
for (int i = 0; i < 3; i++) {
    var temp = i;
    filters.Add(x => x == temp); 
}

四、性能优化实战

表达式树的编译(Compile)开销较大,可以通过缓存提升性能:

// 技术栈:C# .NET 6
private static ConcurrentDictionary<string, Func<Product, bool>> _cache = new();

public static Func<Product, bool> BuildFilter(string propertyName, object value) {
    var key = $"{propertyName}_{value}";
    return _cache.GetOrAdd(key, k => {
        var param = Expression.Parameter(typeof(Product), "p");
        var prop = Expression.Property(param, propertyName);
        var compare = Expression.Equal(prop, Expression.Constant(value));
        return Expression.Lambda<Func<Product, bool>>(compare, param).Compile();
    });
}

五、总结与选型建议

适用场景

  • 需要动态生成代码逻辑(如规则引擎)
  • 替代反射提升性能
  • 构建动态查询条件

优缺点

  • 👍 灵活性强,性能优于反射
  • 👎 学习曲线陡峭,调试困难

注意事项

  1. 优先使用Expression<T>而非LambdaExpression以获得类型安全
  2. 复杂表达式建议分步构建
  3. 避免频繁编译,尽量复用已编译的委托

通过合理使用表达式树,你可以让代码像乐高积木一样灵活组装,应对各种动态场景的需求。