上篇post介绍了表达式树,本节介绍如何动态构建表达式树。
代码动态构建表达式树
在表达式树(Expression Trees)这一文章中我们编写的代码都是让C#编译器把Lambda表达式转换表达式树,这些表达式是硬编码的。
我们通过代码编写表达式:Expression<Func<Book,bool>> e = b =>b.Price > 5
1 | // 手动构建:b => b.Price > 5 |
构建表达式树常用工厂方法
Expression工厂方法
基础节点构建(参数、常量、默认值)
工厂方法 功能描述 示例代码 节点类型 Expression.Parameter创建参数节点(Lambda 形参) var b = Expression.Parameter(typeof(Book), "b");ParameterExpressionExpression.Constant创建常量节点(值、字面量) var five = Expression.Constant(5m, typeof(decimal));ConstantExpressionExpression.Default创建类型默认值 var defaultBook = Expression.Default(typeof(Book));DefaultExpression成员访问(属性 / 字段)
工厂方法 功能描述 示例代码 节点类型 Expression.Property访问属性 var priceProp = Expression.Property(b, nameof(Book.Price));MemberExpressionExpression.PropertyOrField统一属性或字段访问 var titleMember = Expression.PropertyOrField(b, "Title");MemberExpressionExpression.Field访问字段 var authorField = Expression.Field(b, "_author");MemberExpression二元运算(算术 / 比较 / 逻辑)
工厂方法 功能 示例 ExpressionType Expression.Add加法 Expression.Add(priceProp, five)Add Expression.Subtract减法 Expression.Subtract(priceProp, five)Subtract Expression.GreaterThan大于 Expression.GreaterThan(priceProp, five)GreaterThan Expression.Equal等于 Expression.Equal(Expression.Property(b,"Title"), Expression.Constant("C#入门"))Equal Expression.AndAlso逻辑与(短路) Expression.AndAlso(cond1, cond2)AndAlso Expression.OrElse逻辑或(短路) Expression.OrElse(cond1, cond2)OrElse Expression.MakeBinary通用入口 Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, priceProp, five)任意二元 组合示例:
1
2
3
4
5
6var b = Expression.Parameter(typeof(Book), "b");
var priceProp = Expression.Property(b, nameof(Book.Price));
var five = Expression.Constant(5m);
var cond1 = Expression.GreaterThan(priceProp, five);
var cond2 = Expression.Equal(Expression.Property(b, "Title"), Expression.Constant("C#"));
var andExpr = Expression.AndAlso(cond1, cond2);Lambda 表达式包装
工厂方法 功能描述 示例 返回类型 Expression.Lambda<TDelegate>推荐用法 Expression<Func<Book,bool>> e = Expression.Lambda<Func<Book,bool>>(cond1, b);Expression<Func<Book,bool>>Expression.Lambda非泛型 LambdaExpression raw = Expression.Lambda(cond1, b);LambdaExpression方法调用(实例 / 静态)
工厂方法 功能 示例(实例 / 静态) 节点类型 Expression.Call调用方法 见示例 MethodCallExpression1
2
3
4
5
6
7
8
9
10var b = Expression.Parameter(typeof(Book), "b");
var titleProp = Expression.Property(b, nameof(Book.Title));
// 实例方法 b.Title.Contains("C#")
var containsMethod = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) });
var containsCall = Expression.Call(titleProp, containsMethod, Expression.Constant("C#"));
// 静态方法 string.IsNullOrEmpty(b.Title)
var isNullOrEmptyMethod = typeof(string).GetMethod(nameof(string.IsNullOrEmpty), new[] { typeof(string) });
var nullCheckCall = Expression.Call(isNullOrEmptyMethod, titleProp);一元运算与类型转换
工厂方法 功能 示例 节点类型 Expression.Convert强制转换 Expression.Convert(Expression.Constant(5), typeof(decimal));UnaryExpression Expression.Not逻辑非 Expression.Not(cond1)UnaryExpression Expression.TypeAs安全转换 Expression.TypeAs(Expression.Parameter(typeof(object),"obj"), typeof(Book));UnaryExpression 对象与集合创建
工厂方法 功能 示例 节点类型 Expression.New构造对象 有 / 无参 NewExpressionExpression.NewArrayInit创建数组并初始化 Expression.NewArrayInit(typeof(string), Expression.Constant("A"), Expression.Constant("B"));NewArrayExpressionExpression.ArrayIndex数组索引访问 Expression.ArrayIndex(arrParam, Expression.Constant(0));BinaryExpression有参构造示例:
1
2
3
4var titleConst = Expression.Constant("C#进阶");
var priceConst = Expression.Constant(79.9m);
var ctor = typeof(Book).GetConstructor(new[] { typeof(string), typeof(decimal) });
var newBookExpr = Expression.New(ctor, titleConst, priceConst);数组访问示例:
1
2var arrParam = Expression.Parameter(typeof(string[]), "arr");
var first = Expression.ArrayIndex(arrParam, Expression.Constant(0));
用ExpressionTreeToString简化动态构建表达式树
ExpressionTreeToString提供了ToString(“Factory methods”,”C#”),执行会输出类似工厂方法生成表达式树的代码。
1 | Expression<Func<Book, bool>> e = b => b.Price > 5; |
通过ToString("Factory methods","C#")输出动态构建表达式树
1 | using ExpressionTreeToString; |
让构建”动态”起来
上面代码动态构建出了表达式树,但逻辑是固定的。既然逻辑是固定的,为什么不直接硬编码Lambda表达式?动态构建表达式树的价值就在于,运行时根据条件的不同生成不同的表达式树。
场景示例:动态查询条件
假设有一个图书检索系统,用户可以选择多个筛选条件:
1 | class BookSearchCriteria |
实现动态表达式构建器
1 | static Expression<Func<Book, bool>> BuildDynamicPredicate(BookSearchCriteria criteria) |
使用动态构建的表达式
1 | static void DynamicQueryExample() |
移除冗余的 True 条件
上面的实现会产生 (True AndAlso ...) 这样的冗余表达式。可以优化:
1 | static Expression<Func<Book, bool>> BuildDynamicPredicateOptimized(BookSearchCriteria criteria) |
汇总代码(在program.cs里面粘贴下面代码可直接运行)
1 | using System; |
执行dotnet run 输出以下结果
1 | PS D:\source\repo\ConsoleApp1> dotnet run --project "ConsoleApp1" |
动态构建的优势总结
| 场景 | 传统方式 | 动态表达式树 |
|---|---|---|
| 固定条件查询 | books.Where(b => b.Price > 50) |
无需使用,直接 Lambda 即可 |
| 用户可选条件 | 需要写大量 if-else 拼接 SQL/LINQ | 根据条件动态组合表达式 |
| 规则引擎 | 硬编码规则 | 配置驱动,动态生成规则 |
| 权限过滤 | 在业务层手动过滤 | 统一在查询层注入权限表达式 |
| 低代码平台 | 无法实现 | 可视化拖拽转表达式树 |
性能注意事项
缓存编译结果:表达式编译 (
Compile()) 成本较高1
2
3
4
5
6private static readonly Dictionary<string, Func<Book, bool>> _compiledCache = new();
var key = predicate.ToString();
if (!_compiledCache.ContainsKey(key))
_compiledCache[key] = predicate.Compile();
var func = _compiledCache[key];避免过度使用:简单固定查询直接用 Lambda
EF Core 翻译限制:确保表达式可以被翻译成 SQL(避免调用本地方法)