一、表达式树是什么?
想象你要组装一台电脑,但不想提前买好所有配件,而是根据用户需求动态选择CPU、内存等部件。表达式树就像这个"组装说明书",它允许我们在运行时动态构建代码逻辑,而不是在编译时写死。
在C#中,表达式树(Expression Tree)是一种将代码表示为数据结构的方式。比如 x => x > 0 这个Lambda表达式,背后就是一个由ParameterExpression、BinaryExpression等节点组成的树状结构。
// 技术栈: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();
});
}
五、总结与选型建议
适用场景:
- 需要动态生成代码逻辑(如规则引擎)
- 替代反射提升性能
- 构建动态查询条件
优缺点:
- 👍 灵活性强,性能优于反射
- 👎 学习曲线陡峭,调试困难
注意事项:
- 优先使用
Expression<T>而非LambdaExpression以获得类型安全 - 复杂表达式建议分步构建
- 避免频繁编译,尽量复用已编译的委托
通过合理使用表达式树,你可以让代码像乐高积木一样灵活组装,应对各种动态场景的需求。
评论