C#.Net-表达式目录树-学习笔记一、本质与核心概念表达式目录树是一种数据结构以树形结构描述代码逻辑。它不是可执行代码而是对代码结构的描述。委托(Delegate)编译后直接生成 IL可以直接执行表达式目录树(Expression Tree)是一个树形数据对象描述了计算关系不能直接执行ExpressionFuncTvsFuncT前者是表达式树后者是委托二者可通过.Compile()单向转换语法限制表达式目录树只能是单行 Lambda不能有大括号和多行语句树形结构类似二叉树每个节点都是一个子表达式可以一层层拆解或拼装// 委托直接可执行 Funcint, int, int func (m, n) m * n 2; // 表达式目录树描述结构需要 Compile() 后才能执行 ExpressionFuncint, int, int exp (m, n) m * n 2; Funcint, int, int compiled exp.Compile(); int result compiled(10, 20); // 202表达式(m, n) m * n 2的树形结构(加法) / \ * 2(常量) / \ m n(参数)二、动态拼装表达式目录树拼装的核心思路把最小粒度的节点逐步组合成完整表达式最后包裹成 Lambda。2.1 基础无参数// 目标() 123 234 ConstantExpression c1 Expression.Constant(123); ConstantExpression c2 Expression.Constant(234); BinaryExpression add Expression.Add(c1, c2); ExpressionFuncint lambda Expression.LambdaFuncint(add); int result lambda.Compile()(); // 3572.2 带参数// 目标m m 1 ParameterExpression paramM Expression.Parameter(typeof(int), m); ConstantExpression c1 Expression.Constant(1); BinaryExpression add Expression.Add(paramM, c1); ExpressionFuncint, int lambda Expression.LambdaFuncint, int(add, paramM); int result lambda.Compile()(5); // 62.3 访问属性/字段// 目标c c.Id 10 ParameterExpression paramC Expression.Parameter(typeof(People), c); // 字段用 Expression.Field属性用 Expression.Property FieldInfo fieldId typeof(People).GetField(Id); MemberExpression idExp Expression.Field(paramC, fieldId); ConstantExpression c10 Expression.Constant(10); BinaryExpression equal Expression.Equal(idExp, c10); ExpressionFuncPeople, bool lambda Expression.LambdaFuncPeople, bool(equal, paramC);2.4 调用方法// 目标c c.Name.Equals(Richard) ParameterExpression paramC Expression.Parameter(typeof(People), c); PropertyInfo propName typeof(People).GetProperty(Name); MemberExpression nameExp Expression.Property(paramC, propName); MethodInfo equalsMethod typeof(string).GetMethod(Equals, new[] { typeof(string) }); ConstantExpression constRichard Expression.Constant(Richard); MethodCallExpression callExp Expression.Call(nameExp, equalsMethod, constRichard); // 包装成 Lambda 并执行 ExpressionFuncPeople, bool lambda Expression.LambdaFuncPeople, bool(callExp, paramC); bool result lambda.Compile()(new People { Name Richard }); // true2.5 复杂表达式建议从右往左拼装// 目标c c.Id.ToString() 10 c.Name.Equals(Richard) c.Age 35 ParameterExpression paramC Expression.Parameter(typeof(People), c); // 1. c.Age 35 var ageExp Expression.Property(paramC, typeof(People).GetProperty(Age)); var ageGreater Expression.GreaterThan(ageExp, Expression.Constant(35)); // 2. c.Name.Equals(Richard) var nameExp Expression.Property(paramC, typeof(People).GetProperty(Name)); var equalsMethod typeof(string).GetMethod(Equals, new[] { typeof(string) }); var nameEquals Expression.Call(nameExp, equalsMethod, Expression.Constant(Richard)); // 3. c.Id.ToString() 10 var idExp Expression.Field(paramC, typeof(People).GetField(Id)); var toStringMethod typeof(int).GetMethod(ToString, Type.EmptyTypes); var toStringExp Expression.Call(idExp, toStringMethod); var idEqual Expression.Equal(toStringExp, Expression.Constant(10)); // 4. 用 AndAlso 连接(对应 ) var and1 Expression.AndAlso(idEqual, nameEquals); var finalExp Expression.AndAlso(and1, ageGreater); ExpressionFuncPeople, bool lambda Expression.LambdaFuncPeople, bool(finalExp, paramC);技巧遇到复杂表达式可以先用快捷 Lambda 写出来再用 ILSpy 等反编译工具查看中间语言照着拼装。三、表达式访问者(ExpressionVisitor)3.1 核心机制ExpressionVisitor是遍历和修改表达式目录树的工具采用访问者模式。Visit(expression)入口方法判断节点类型后分发到对应的VisitXxx方法表达式树是二叉树结构Visit会递归遍历到所有叶节点重写VisitXxx方法可以在任意节点拦截、读取或修改内容返回新节点即可修改表达式树返回原节点则保持不变3.2 修改表达式树(OperationsVisitor)通过重写VisitBinary可以在遍历时把加法替换成减法、乘法替换成除法public class OperationsVisitor : ExpressionVisitor { public Expression Modify(Expression expression) { return Visit(expression); } protected override Expression VisitBinary(BinaryExpression b) { if (b.NodeType ExpressionType.Add) { Expression left Visit(b.Left); Expression right Visit(b.Right); return Expression.Subtract(left, right); // 加法 → 减法 } if (b.NodeType ExpressionType.Multiply) { Expression left Visit(b.Left); Expression right Visit(b.Right); return Expression.Divide(left, right); // 乘法 → 除法 } return base.VisitBinary(b); } } // 使用 ExpressionFuncint, int, int exp (m, n) m * n 2; var visitor new OperationsVisitor(); Expression modified visitor.Modify(exp); // 原来 m * n 2修改后变成 m / n - 23.3 解析表达式为 SQL(ConditionBuilderVisitor)用栈来收集每个节点转换出的 SQL 片段最终拼接成完整条件public class ConditionBuilderVisitor : ExpressionVisitor { private Stackstring _StringStack new Stackstring(); public string Condition() { string condition string.Concat(_StringStack.ToArray()); _StringStack.Clear(); return condition; } // 二元表达式a b、a b 等 protected override Expression VisitBinary(BinaryExpression node) { _StringStack.Push()); Visit(node.Right); // ToSqlOperator() 是自定义扩展方法将 ExpressionType 映射为 SQL 运算符 // 如 AndAlso/And → ANDEqual → GreaterThan → 等(见本章末尾映射表) _StringStack.Push( node.NodeType.ToSqlOperator() ); Visit(node.Left); _StringStack.Push((); return node; } // 成员访问c.Age、c.Name // 注意如果是外部变量(闭包捕获)需要通过反射取值而不是直接输出字段名 protected override Expression VisitMember(MemberExpression node) { if (node.Expression is ConstantExpression) { // 外部变量通过反射获取实际值 var obj (node.Expression as ConstantExpression).Value; var value (node.Member as FieldInfo).GetValue(obj); _StringStack.Push(${value}); } else { // 实体属性输出列名 _StringStack.Push($ [{node.Member.Name}] ); } return node; } // 常量10、abc protected override Expression VisitConstant(ConstantExpression node) { _StringStack.Push($ {node.Value} ); return node; } // 方法调用Contains、StartsWith、EndsWith protected override Expression VisitMethodCall(MethodCallExpression m) { string format m.Method.Name switch { StartsWith ({0} LIKE {1}%), Contains ({0} LIKE %{1}%), EndsWith ({0} LIKE %{1}), _ throw new NotSupportedException(m.NodeType is not supported!) }; Visit(m.Object); Visit(m.Arguments[0]); string right _StringStack.Pop(); string left _StringStack.Pop(); _StringStack.Push(string.Format(format, left, right)); return m; } }ExpressionType 到 SQL 运算符的映射(SqlOperator)ExpressionTypeSQLAndAlso/AndANDOrElse/OrOREqualNotEqualGreaterThanGreaterThanOrEqualLessThanLessThanOrEqual使用示例ExpressionFuncPeople, bool lambda x x.Age 5 x.Name.StartsWith(A) x.Name.Contains(B) x.Name.EndsWith(C); var visitor new ConditionBuilderVisitor(); visitor.Visit(lambda); Console.WriteLine(visitor.Condition()); // 输出: ((( [Age] 5) AND ([Name] LIKE A%)) AND ([Name] LIKE %B%)) AND ([Name] LIKE %C))外部变量捕获的处理string name AAA; ExpressionFuncPeople, bool lambda x x.Age 5 x.Name name || x.Id 5; // name 是外部变量编译后会被包装成 ConstantExpression 的字段 // VisitMember 中检测到 node.Expression is ConstantExpression通过反射取出 AAA3.4 实战扩展BatchDelete将表达式树解析为 SQL WHERE 条件直接用于数据库操作public static void BatchDeleteT(this IQueryableT entities, ExpressionFuncT, bool expr) { var visitor new ConditionBuilderVisitor(); visitor.Visit(expr); string condition visitor.Condition(); string sql $DELETE FROM [{typeof(T).Name}] WHERE {condition}; // 执行 sql... } // 使用 dbSet.BatchDeletePeople(p p.Age 30 p.Name.StartsWith(A)); // 生成: DELETE FROM [People] WHERE (( [Age] 30) AND ([Name] LIKE A%))四、表达式扩展And / Or / Not4.1 为什么需要参数替换两个独立声明的表达式参数对象不是同一个实例直接合并会报错。需要用ExpressionVisitor统一替换成同一个参数ExpressionFuncPeople, bool exp1 x x.Age 5; // 参数是 x ExpressionFuncPeople, bool exp2 y y.Id 5; // 参数是 y不同实例 // 直接 Expression.AndAlso(exp1.Body, exp2.Body) 会出错 // 需要把两个 Body 中的参数都替换成同一个新参数 c4.2 参数替换访问者(NewExpressionVisitor)internal class NewExpressionVisitor : ExpressionVisitor { private readonly ParameterExpression _newParameter; public NewExpressionVisitor(ParameterExpression param) { _newParameter param; } public Expression Replace(Expression exp) Visit(exp); protected override Expression VisitParameter(ParameterExpression node) { return _newParameter; // 把所有参数节点替换成新参数 } }4.3 And / Or / Not 扩展方法public static class ExpressionExtend { public static ExpressionFuncT, bool AndT( this ExpressionFuncT, bool expr1, ExpressionFuncT, bool expr2) { if (expr1 null) return expr2; if (expr2 null) return expr1; ParameterExpression param Expression.Parameter(typeof(T), c); var visitor new NewExpressionVisitor(param); var left visitor.Replace(expr1.Body); var right visitor.Replace(expr2.Body); var body Expression.And(left, right); // 位运算 非短路 return Expression.LambdaFuncT, bool(body, param); } public static ExpressionFuncT, bool OrT( this ExpressionFuncT, bool expr1, ExpressionFuncT, bool expr2) { ParameterExpression param Expression.Parameter(typeof(T), c); var visitor new NewExpressionVisitor(param); var left visitor.Replace(expr1.Body); var right visitor.Replace(expr2.Body); var body Expression.Or(left, right); // 位运算 |非短路 return Expression.LambdaFuncT, bool(body, param); } public static ExpressionFuncT, bool NotT( this ExpressionFuncT, bool expr) { var body Expression.Not(expr.Body); return Expression.LambdaFuncT, bool(body, expr.Parameters[0]); } }注意Expression.And对应位运算(非短路)Expression.AndAlso对应(短路)。源码中And扩展方法用的是Expression.AndOr用的是Expression.Or。使用示例ExpressionFuncPeople, bool exp1 x x.Age 5; ExpressionFuncPeople, bool exp2 x x.Id 5; var andExp exp1.And(exp2); // c c.Age 5 c.Id 5 var orExp exp1.Or(exp2); // c c.Age 5 | c.Id 5 var notExp exp1.Not(); // x !(x.Age 5) // 动态构建查询条件 ExpressionFuncPeople, bool query null; string name Console.ReadLine(); if (!string.IsNullOrWhiteSpace(name)) query query.And(p p.Name.Contains(name)); string ageStr Console.ReadLine(); if (int.TryParse(ageStr, out int age)) query query.And(p p.Age age); var result dbSet.Where(query).ToList();五、对象映射(高性能 Mapper)5.1 五种方案对比方案性能灵活性说明硬编码⭐⭐⭐⭐⭐⭐手写赋值不通用反射(ReflectionMapper)⭐⭐⭐⭐⭐⭐每次都要反射慢序列化(SerializeMapper)⭐⭐⭐⭐⭐JSON 序列化再反序列化慢表达式树字典缓存(ExpressionMapper)⭐⭐⭐⭐⭐⭐⭐⭐⭐首次拼装后续走字典取委托表达式树泛型缓存(ExpressionGenericMapper)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐最优泛型副本天然隔离5.2 反射方案(最慢)public static TOut TransTIn, TOut(TIn tIn) { TOut tOut Activator.CreateInstanceTOut(); foreach (var prop in tOut.GetType().GetProperties()) { var sourceProp tIn.GetType().GetProperty(prop.Name); prop.SetValue(tOut, sourceProp.GetValue(tIn)); } foreach (var field in tOut.GetType().GetFields()) { var sourceField tIn.GetType().GetField(field.Name); field.SetValue(tOut, sourceField.GetValue(tIn)); } return tOut; }5.3 序列化方案(慢)public static TOut TransTIn, TOut(TIn tIn) { string json JsonConvert.SerializeObject(tIn); return JsonConvert.DeserializeObjectTOut(json); }5.4 表达式树字典缓存(ExpressionMapper)用Dictionarystring, object缓存编译好的委托key 是类型组合字符串。首次调用拼装并编译后续直接从字典取委托执行private static Dictionarystring, object _Dic new Dictionarystring, object(); public static TOut TransTIn, TOut(TIn tIn) { string key $funckey_{typeof(TIn).FullName}_{typeof(TOut).FullName}; if (!_Dic.ContainsKey(key)) { // 拼装表达式p new TOut { Prop1 p.Prop1, ... } ParameterExpression param Expression.Parameter(typeof(TIn), p); ListMemberBinding bindings new ListMemberBinding(); foreach (var prop in typeof(TOut).GetProperties()) { var sourceProp Expression.Property(param, typeof(TIn).GetProperty(prop.Name)); bindings.Add(Expression.Bind(prop, sourceProp)); } foreach (var field in typeof(TOut).GetFields()) { var sourceField Expression.Field(param, typeof(TIn).GetField(field.Name)); bindings.Add(Expression.Bind(field, sourceField)); } var memberInit Expression.MemberInit(Expression.New(typeof(TOut)), bindings.ToArray()); var lambda Expression.LambdaFuncTIn, TOut(memberInit, param); _Dic[key] lambda.Compile(); } return ((FuncTIn, TOut)_Dic[key]).Invoke(tIn); }注意_Dic的ContainsKey 写入不是原子操作多线程并发时存在竞态条件。生产环境建议改用ConcurrentDictionary或加锁或直接使用泛型缓存方案。5.5 表达式树泛型缓存(ExpressionGenericMapper最优)泛型类的静态字段天然为每组类型生成独立副本无需字典查找性能更高public class ExpressionGenericMapperTIn, TOut { private static readonly FuncTIn, TOut _FUNC; static ExpressionGenericMapper() { ParameterExpression param Expression.Parameter(typeof(TIn), p); ListMemberBinding bindings new ListMemberBinding(); // 处理属性 foreach (var prop in typeof(TOut).GetProperties()) { var sourceProp Expression.Property(param, typeof(TIn).GetProperty(prop.Name)); bindings.Add(Expression.Bind(prop, sourceProp)); } // 处理字段 foreach (var field in typeof(TOut).GetFields()) { var sourceField Expression.Field(param, typeof(TIn).GetField(field.Name)); bindings.Add(Expression.Bind(field, sourceField)); } var memberInit Expression.MemberInit(Expression.New(typeof(TOut)), bindings.ToArray()); var lambda Expression.LambdaFuncTIn, TOut(memberInit, param); _FUNC lambda.Compile(); // 只执行一次 } public static TOut Trans(TIn t) _FUNC(t); } // 使用 PeopleCopy copy ExpressionGenericMapperPeople, PeopleCopy.Trans(people);泛型缓存的原理ExpressionGenericMapperPeople, PeopleCopy和ExpressionGenericMapperUser, UserDto是两个不同的类各自有独立的静态字段_FUNCCLR 保证每组类型只初始化一次。六、核心知识点总结6.1 为什么要用表达式目录树动态性在运行时动态构建查询逻辑把程序写活可分析性可以解析表达式结构转换为 SQL、MongoDB 查询、ES DSL 等类型安全编译时检查避免字符串拼接 SQL 的错误和注入风险高性能配合缓存性能接近硬编码6.2 常用表达式节点速查类型说明创建示例ConstantExpression常量Expression.Constant(10)ParameterExpression参数Expression.Parameter(typeof(int), x)BinaryExpression二元运算(加减乘除、比较、逻辑)Expression.Add(a, b)/Expression.GreaterThan(a, b)MemberExpression成员访问(属性/字段)Expression.Property(obj, Name)/Expression.Field(obj, fi)MethodCallExpression方法调用Expression.Call(obj, method, args)MemberInitExpression对象初始化Expression.MemberInit(newExp, bindings)LambdaExpressionLambda 整体Expression.LambdaFunc...(body, params)6.3 实际应用场景ORM 框架Entity Framework、Dapper 等将 LINQ 表达式树解析为 SQL动态查询根据用户输入拼装 WHERE 条件替代字符串拼接 SQL对象映射AutoMapper 底层原理动态生成硬编码级别的映射委托规则引擎运行时动态构建业务规则批量操作扩展如BatchDeleteT直接从表达式生成 DELETE SQL代码生成运行时动态生成高性能执行逻辑6.4 学习建议理解本质表达式树是数据结构不是可执行代码从简单开始先掌握基础拼装再学习复杂场景善用反编译用 ILSpy 等工具查看快捷 Lambda 编译后的中间语言照着拼装性能意识合理使用缓存避免重复拼装优先选泛型缓存而非字典缓存记住表达式目录树 动态 类型安全 高性能
06-C#
C#.Net-表达式目录树-学习笔记一、本质与核心概念表达式目录树是一种数据结构以树形结构描述代码逻辑。它不是可执行代码而是对代码结构的描述。委托(Delegate)编译后直接生成 IL可以直接执行表达式目录树(Expression Tree)是一个树形数据对象描述了计算关系不能直接执行ExpressionFuncTvsFuncT前者是表达式树后者是委托二者可通过.Compile()单向转换语法限制表达式目录树只能是单行 Lambda不能有大括号和多行语句树形结构类似二叉树每个节点都是一个子表达式可以一层层拆解或拼装// 委托直接可执行 Funcint, int, int func (m, n) m * n 2; // 表达式目录树描述结构需要 Compile() 后才能执行 ExpressionFuncint, int, int exp (m, n) m * n 2; Funcint, int, int compiled exp.Compile(); int result compiled(10, 20); // 202表达式(m, n) m * n 2的树形结构(加法) / \ * 2(常量) / \ m n(参数)二、动态拼装表达式目录树拼装的核心思路把最小粒度的节点逐步组合成完整表达式最后包裹成 Lambda。2.1 基础无参数// 目标() 123 234 ConstantExpression c1 Expression.Constant(123); ConstantExpression c2 Expression.Constant(234); BinaryExpression add Expression.Add(c1, c2); ExpressionFuncint lambda Expression.LambdaFuncint(add); int result lambda.Compile()(); // 3572.2 带参数// 目标m m 1 ParameterExpression paramM Expression.Parameter(typeof(int), m); ConstantExpression c1 Expression.Constant(1); BinaryExpression add Expression.Add(paramM, c1); ExpressionFuncint, int lambda Expression.LambdaFuncint, int(add, paramM); int result lambda.Compile()(5); // 62.3 访问属性/字段// 目标c c.Id 10 ParameterExpression paramC Expression.Parameter(typeof(People), c); // 字段用 Expression.Field属性用 Expression.Property FieldInfo fieldId typeof(People).GetField(Id); MemberExpression idExp Expression.Field(paramC, fieldId); ConstantExpression c10 Expression.Constant(10); BinaryExpression equal Expression.Equal(idExp, c10); ExpressionFuncPeople, bool lambda Expression.LambdaFuncPeople, bool(equal, paramC);2.4 调用方法// 目标c c.Name.Equals(Richard) ParameterExpression paramC Expression.Parameter(typeof(People), c); PropertyInfo propName typeof(People).GetProperty(Name); MemberExpression nameExp Expression.Property(paramC, propName); MethodInfo equalsMethod typeof(string).GetMethod(Equals, new[] { typeof(string) }); ConstantExpression constRichard Expression.Constant(Richard); MethodCallExpression callExp Expression.Call(nameExp, equalsMethod, constRichard); // 包装成 Lambda 并执行 ExpressionFuncPeople, bool lambda Expression.LambdaFuncPeople, bool(callExp, paramC); bool result lambda.Compile()(new People { Name Richard }); // true2.5 复杂表达式建议从右往左拼装// 目标c c.Id.ToString() 10 c.Name.Equals(Richard) c.Age 35 ParameterExpression paramC Expression.Parameter(typeof(People), c); // 1. c.Age 35 var ageExp Expression.Property(paramC, typeof(People).GetProperty(Age)); var ageGreater Expression.GreaterThan(ageExp, Expression.Constant(35)); // 2. c.Name.Equals(Richard) var nameExp Expression.Property(paramC, typeof(People).GetProperty(Name)); var equalsMethod typeof(string).GetMethod(Equals, new[] { typeof(string) }); var nameEquals Expression.Call(nameExp, equalsMethod, Expression.Constant(Richard)); // 3. c.Id.ToString() 10 var idExp Expression.Field(paramC, typeof(People).GetField(Id)); var toStringMethod typeof(int).GetMethod(ToString, Type.EmptyTypes); var toStringExp Expression.Call(idExp, toStringMethod); var idEqual Expression.Equal(toStringExp, Expression.Constant(10)); // 4. 用 AndAlso 连接(对应 ) var and1 Expression.AndAlso(idEqual, nameEquals); var finalExp Expression.AndAlso(and1, ageGreater); ExpressionFuncPeople, bool lambda Expression.LambdaFuncPeople, bool(finalExp, paramC);技巧遇到复杂表达式可以先用快捷 Lambda 写出来再用 ILSpy 等反编译工具查看中间语言照着拼装。三、表达式访问者(ExpressionVisitor)3.1 核心机制ExpressionVisitor是遍历和修改表达式目录树的工具采用访问者模式。Visit(expression)入口方法判断节点类型后分发到对应的VisitXxx方法表达式树是二叉树结构Visit会递归遍历到所有叶节点重写VisitXxx方法可以在任意节点拦截、读取或修改内容返回新节点即可修改表达式树返回原节点则保持不变3.2 修改表达式树(OperationsVisitor)通过重写VisitBinary可以在遍历时把加法替换成减法、乘法替换成除法public class OperationsVisitor : ExpressionVisitor { public Expression Modify(Expression expression) { return Visit(expression); } protected override Expression VisitBinary(BinaryExpression b) { if (b.NodeType ExpressionType.Add) { Expression left Visit(b.Left); Expression right Visit(b.Right); return Expression.Subtract(left, right); // 加法 → 减法 } if (b.NodeType ExpressionType.Multiply) { Expression left Visit(b.Left); Expression right Visit(b.Right); return Expression.Divide(left, right); // 乘法 → 除法 } return base.VisitBinary(b); } } // 使用 ExpressionFuncint, int, int exp (m, n) m * n 2; var visitor new OperationsVisitor(); Expression modified visitor.Modify(exp); // 原来 m * n 2修改后变成 m / n - 23.3 解析表达式为 SQL(ConditionBuilderVisitor)用栈来收集每个节点转换出的 SQL 片段最终拼接成完整条件public class ConditionBuilderVisitor : ExpressionVisitor { private Stackstring _StringStack new Stackstring(); public string Condition() { string condition string.Concat(_StringStack.ToArray()); _StringStack.Clear(); return condition; } // 二元表达式a b、a b 等 protected override Expression VisitBinary(BinaryExpression node) { _StringStack.Push()); Visit(node.Right); // ToSqlOperator() 是自定义扩展方法将 ExpressionType 映射为 SQL 运算符 // 如 AndAlso/And → ANDEqual → GreaterThan → 等(见本章末尾映射表) _StringStack.Push( node.NodeType.ToSqlOperator() ); Visit(node.Left); _StringStack.Push((); return node; } // 成员访问c.Age、c.Name // 注意如果是外部变量(闭包捕获)需要通过反射取值而不是直接输出字段名 protected override Expression VisitMember(MemberExpression node) { if (node.Expression is ConstantExpression) { // 外部变量通过反射获取实际值 var obj (node.Expression as ConstantExpression).Value; var value (node.Member as FieldInfo).GetValue(obj); _StringStack.Push(${value}); } else { // 实体属性输出列名 _StringStack.Push($ [{node.Member.Name}] ); } return node; } // 常量10、abc protected override Expression VisitConstant(ConstantExpression node) { _StringStack.Push($ {node.Value} ); return node; } // 方法调用Contains、StartsWith、EndsWith protected override Expression VisitMethodCall(MethodCallExpression m) { string format m.Method.Name switch { StartsWith ({0} LIKE {1}%), Contains ({0} LIKE %{1}%), EndsWith ({0} LIKE %{1}), _ throw new NotSupportedException(m.NodeType is not supported!) }; Visit(m.Object); Visit(m.Arguments[0]); string right _StringStack.Pop(); string left _StringStack.Pop(); _StringStack.Push(string.Format(format, left, right)); return m; } }ExpressionType 到 SQL 运算符的映射(SqlOperator)ExpressionTypeSQLAndAlso/AndANDOrElse/OrOREqualNotEqualGreaterThanGreaterThanOrEqualLessThanLessThanOrEqual使用示例ExpressionFuncPeople, bool lambda x x.Age 5 x.Name.StartsWith(A) x.Name.Contains(B) x.Name.EndsWith(C); var visitor new ConditionBuilderVisitor(); visitor.Visit(lambda); Console.WriteLine(visitor.Condition()); // 输出: ((( [Age] 5) AND ([Name] LIKE A%)) AND ([Name] LIKE %B%)) AND ([Name] LIKE %C))外部变量捕获的处理string name AAA; ExpressionFuncPeople, bool lambda x x.Age 5 x.Name name || x.Id 5; // name 是外部变量编译后会被包装成 ConstantExpression 的字段 // VisitMember 中检测到 node.Expression is ConstantExpression通过反射取出 AAA3.4 实战扩展BatchDelete将表达式树解析为 SQL WHERE 条件直接用于数据库操作public static void BatchDeleteT(this IQueryableT entities, ExpressionFuncT, bool expr) { var visitor new ConditionBuilderVisitor(); visitor.Visit(expr); string condition visitor.Condition(); string sql $DELETE FROM [{typeof(T).Name}] WHERE {condition}; // 执行 sql... } // 使用 dbSet.BatchDeletePeople(p p.Age 30 p.Name.StartsWith(A)); // 生成: DELETE FROM [People] WHERE (( [Age] 30) AND ([Name] LIKE A%))四、表达式扩展And / Or / Not4.1 为什么需要参数替换两个独立声明的表达式参数对象不是同一个实例直接合并会报错。需要用ExpressionVisitor统一替换成同一个参数ExpressionFuncPeople, bool exp1 x x.Age 5; // 参数是 x ExpressionFuncPeople, bool exp2 y y.Id 5; // 参数是 y不同实例 // 直接 Expression.AndAlso(exp1.Body, exp2.Body) 会出错 // 需要把两个 Body 中的参数都替换成同一个新参数 c4.2 参数替换访问者(NewExpressionVisitor)internal class NewExpressionVisitor : ExpressionVisitor { private readonly ParameterExpression _newParameter; public NewExpressionVisitor(ParameterExpression param) { _newParameter param; } public Expression Replace(Expression exp) Visit(exp); protected override Expression VisitParameter(ParameterExpression node) { return _newParameter; // 把所有参数节点替换成新参数 } }4.3 And / Or / Not 扩展方法public static class ExpressionExtend { public static ExpressionFuncT, bool AndT( this ExpressionFuncT, bool expr1, ExpressionFuncT, bool expr2) { if (expr1 null) return expr2; if (expr2 null) return expr1; ParameterExpression param Expression.Parameter(typeof(T), c); var visitor new NewExpressionVisitor(param); var left visitor.Replace(expr1.Body); var right visitor.Replace(expr2.Body); var body Expression.And(left, right); // 位运算 非短路 return Expression.LambdaFuncT, bool(body, param); } public static ExpressionFuncT, bool OrT( this ExpressionFuncT, bool expr1, ExpressionFuncT, bool expr2) { ParameterExpression param Expression.Parameter(typeof(T), c); var visitor new NewExpressionVisitor(param); var left visitor.Replace(expr1.Body); var right visitor.Replace(expr2.Body); var body Expression.Or(left, right); // 位运算 |非短路 return Expression.LambdaFuncT, bool(body, param); } public static ExpressionFuncT, bool NotT( this ExpressionFuncT, bool expr) { var body Expression.Not(expr.Body); return Expression.LambdaFuncT, bool(body, expr.Parameters[0]); } }注意Expression.And对应位运算(非短路)Expression.AndAlso对应(短路)。源码中And扩展方法用的是Expression.AndOr用的是Expression.Or。使用示例ExpressionFuncPeople, bool exp1 x x.Age 5; ExpressionFuncPeople, bool exp2 x x.Id 5; var andExp exp1.And(exp2); // c c.Age 5 c.Id 5 var orExp exp1.Or(exp2); // c c.Age 5 | c.Id 5 var notExp exp1.Not(); // x !(x.Age 5) // 动态构建查询条件 ExpressionFuncPeople, bool query null; string name Console.ReadLine(); if (!string.IsNullOrWhiteSpace(name)) query query.And(p p.Name.Contains(name)); string ageStr Console.ReadLine(); if (int.TryParse(ageStr, out int age)) query query.And(p p.Age age); var result dbSet.Where(query).ToList();五、对象映射(高性能 Mapper)5.1 五种方案对比方案性能灵活性说明硬编码⭐⭐⭐⭐⭐⭐手写赋值不通用反射(ReflectionMapper)⭐⭐⭐⭐⭐⭐每次都要反射慢序列化(SerializeMapper)⭐⭐⭐⭐⭐JSON 序列化再反序列化慢表达式树字典缓存(ExpressionMapper)⭐⭐⭐⭐⭐⭐⭐⭐⭐首次拼装后续走字典取委托表达式树泛型缓存(ExpressionGenericMapper)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐最优泛型副本天然隔离5.2 反射方案(最慢)public static TOut TransTIn, TOut(TIn tIn) { TOut tOut Activator.CreateInstanceTOut(); foreach (var prop in tOut.GetType().GetProperties()) { var sourceProp tIn.GetType().GetProperty(prop.Name); prop.SetValue(tOut, sourceProp.GetValue(tIn)); } foreach (var field in tOut.GetType().GetFields()) { var sourceField tIn.GetType().GetField(field.Name); field.SetValue(tOut, sourceField.GetValue(tIn)); } return tOut; }5.3 序列化方案(慢)public static TOut TransTIn, TOut(TIn tIn) { string json JsonConvert.SerializeObject(tIn); return JsonConvert.DeserializeObjectTOut(json); }5.4 表达式树字典缓存(ExpressionMapper)用Dictionarystring, object缓存编译好的委托key 是类型组合字符串。首次调用拼装并编译后续直接从字典取委托执行private static Dictionarystring, object _Dic new Dictionarystring, object(); public static TOut TransTIn, TOut(TIn tIn) { string key $funckey_{typeof(TIn).FullName}_{typeof(TOut).FullName}; if (!_Dic.ContainsKey(key)) { // 拼装表达式p new TOut { Prop1 p.Prop1, ... } ParameterExpression param Expression.Parameter(typeof(TIn), p); ListMemberBinding bindings new ListMemberBinding(); foreach (var prop in typeof(TOut).GetProperties()) { var sourceProp Expression.Property(param, typeof(TIn).GetProperty(prop.Name)); bindings.Add(Expression.Bind(prop, sourceProp)); } foreach (var field in typeof(TOut).GetFields()) { var sourceField Expression.Field(param, typeof(TIn).GetField(field.Name)); bindings.Add(Expression.Bind(field, sourceField)); } var memberInit Expression.MemberInit(Expression.New(typeof(TOut)), bindings.ToArray()); var lambda Expression.LambdaFuncTIn, TOut(memberInit, param); _Dic[key] lambda.Compile(); } return ((FuncTIn, TOut)_Dic[key]).Invoke(tIn); }注意_Dic的ContainsKey 写入不是原子操作多线程并发时存在竞态条件。生产环境建议改用ConcurrentDictionary或加锁或直接使用泛型缓存方案。5.5 表达式树泛型缓存(ExpressionGenericMapper最优)泛型类的静态字段天然为每组类型生成独立副本无需字典查找性能更高public class ExpressionGenericMapperTIn, TOut { private static readonly FuncTIn, TOut _FUNC; static ExpressionGenericMapper() { ParameterExpression param Expression.Parameter(typeof(TIn), p); ListMemberBinding bindings new ListMemberBinding(); // 处理属性 foreach (var prop in typeof(TOut).GetProperties()) { var sourceProp Expression.Property(param, typeof(TIn).GetProperty(prop.Name)); bindings.Add(Expression.Bind(prop, sourceProp)); } // 处理字段 foreach (var field in typeof(TOut).GetFields()) { var sourceField Expression.Field(param, typeof(TIn).GetField(field.Name)); bindings.Add(Expression.Bind(field, sourceField)); } var memberInit Expression.MemberInit(Expression.New(typeof(TOut)), bindings.ToArray()); var lambda Expression.LambdaFuncTIn, TOut(memberInit, param); _FUNC lambda.Compile(); // 只执行一次 } public static TOut Trans(TIn t) _FUNC(t); } // 使用 PeopleCopy copy ExpressionGenericMapperPeople, PeopleCopy.Trans(people);泛型缓存的原理ExpressionGenericMapperPeople, PeopleCopy和ExpressionGenericMapperUser, UserDto是两个不同的类各自有独立的静态字段_FUNCCLR 保证每组类型只初始化一次。六、核心知识点总结6.1 为什么要用表达式目录树动态性在运行时动态构建查询逻辑把程序写活可分析性可以解析表达式结构转换为 SQL、MongoDB 查询、ES DSL 等类型安全编译时检查避免字符串拼接 SQL 的错误和注入风险高性能配合缓存性能接近硬编码6.2 常用表达式节点速查类型说明创建示例ConstantExpression常量Expression.Constant(10)ParameterExpression参数Expression.Parameter(typeof(int), x)BinaryExpression二元运算(加减乘除、比较、逻辑)Expression.Add(a, b)/Expression.GreaterThan(a, b)MemberExpression成员访问(属性/字段)Expression.Property(obj, Name)/Expression.Field(obj, fi)MethodCallExpression方法调用Expression.Call(obj, method, args)MemberInitExpression对象初始化Expression.MemberInit(newExp, bindings)LambdaExpressionLambda 整体Expression.LambdaFunc...(body, params)6.3 实际应用场景ORM 框架Entity Framework、Dapper 等将 LINQ 表达式树解析为 SQL动态查询根据用户输入拼装 WHERE 条件替代字符串拼接 SQL对象映射AutoMapper 底层原理动态生成硬编码级别的映射委托规则引擎运行时动态构建业务规则批量操作扩展如BatchDeleteT直接从表达式生成 DELETE SQL代码生成运行时动态生成高性能执行逻辑6.4 学习建议理解本质表达式树是数据结构不是可执行代码从简单开始先掌握基础拼装再学习复杂场景善用反编译用 ILSpy 等工具查看快捷 Lambda 编译后的中间语言照着拼装性能意识合理使用缓存避免重复拼装优先选泛型缓存而非字典缓存记住表达式目录树 动态 类型安全 高性能