表达式树
1、表达式树定义
先看一下微软官方文档,官方文档对表达式的解释:
表达式树是定义代码的数据结构。 表达式树基于编译器用于分析代码和生成已编译输出的相同结构。比如说下面这个例子
var sum = 1 + 2;
画成图的话就张这样
直观地看,整个语句是一棵树:应从根节点开始,浏览到树中的每个节点,以查看构成该语句的代码:
- 具有赋值 (
var sum = 1 + 2;
) 的变量声明语句- 隐式变量类型声明 (
var sum
)- 隐式 var 关键字 (
var
) - 变量名称声明 (
sum
)
- 隐式 var 关键字 (
- 赋值运算符 (
=
) - 二进制加法表达式 (
1 + 2
)- 左操作数 (
1
) - 加法运算符 (
+
) - 右操作数 (
2
)
- 左操作数 (
- 隐式变量类型声明 (
2、表达式树创建
这个直接用官方的示例
// Addition is an add expression for "1 + 2"ConstantExpression one = Expression.Constant(1, typeof(int));ConstantExpression two = Expression.Constant(2, typeof(int));BinaryExpression addition = Expression.Add(one, two);Console.WriteLine(addition); // 输出 (1 + 2)
3、表达式树的执行
只能执行表示 lambda 表达式的表达式树。表示 lambda 表达式的表达式树的类型为 LambdaExpression 或 ExpressionBinaryExpression addition = Expression.Add(one, two);
那就要转换成委托然后调用,所以需要:
ConstantExpression one = Expression.Constant(1, typeof(int));ConstantExpression two = Expression.Constant(2, typeof(int));BinaryExpression addition = Expression.Add(one, two);Console.WriteLine(addition); // 输出 (1 + 2)Expression<Func<int>> compiledAddition = Expression.Lambda<Func<int>>(addition);Func<int> additionFunc = compiledAddition.Compile();Console.WriteLine(additionFunc()); // 输出 3
这样就可以执行一个没有入参的表达式树了,那怎么执行一个有参数的表达式树呢?
整个示例:
// 创建参数表达式ParameterExpression xParameter = Expression.Parameter(typeof(double), "x");ParameterExpression yParameter = Expression.Parameter(typeof(double), "y");
// 创建 x * x 和 y * y 的表达式BinaryExpression xSquared = Expression.Multiply(xParameter, xParameter); x*xBinaryExpression ySquared = Expression.Multiply(yParameter, yParameter); y*y
// 创建 x * x + y * y 的表达式BinaryExpression sum = Expression.Add(xSquared, ySquared);
Expression<Func<double, double, double>> expressionSum=Expression.Lambda<Func<double, double, double>>(sum, xParameter, yParameter);Func<double, double, double> compiledSum = expressionSum.Compile();Console.WriteLine(compiledSum(3, 4)); // 输出 25
这样就OK了,需要注意的是,下面就把常用的Expression类展示一下
//ParameterExpression: 表示表达式树中的参数。ParameterExpression param = Expression.Parameter(typeof(int), "x");
//ConstantExpression: 表示表达式树中的常量ConstantExpression constant = Expression.Constant(5);
//BinaryExpression: 表示具有二元运算符的表达式,如加法、减法、乘法和除法BinaryExpression add = Expression.Add(param, constant); //x+5
//UnaryExpression: 表示具有一元运算符的表达式,如取负、逻辑非等。UnaryExpression neg = Expression.Negate(param); // 创建一元表达式 -x
//MethodCallExpression: 表示方法调用的表达式。MethodInfo methodInfo = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) });MethodCallExpression methodCall = Expression.Call(methodInfo, Expression.Constant(16.0)); //Math.Sqrt(16.0) 根号16 √16
//LambdaExpression: 表示 Lambda 表达式。Expression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(add, param, param);
//MemberExpression: 表示对字段或属性的访问MemberExpression member = Expression.Property(param, "Length");
//ConditionalExpression: 表示条件表达式(类似于三元运算符)ConditionalExpression condition = Expression.Condition( Expression.GreaterThan(param, Expression.Constant(10)), Expression.Constant("Greater"), Expression.Constant("Lesser"));//创建条件表达式 x > 10 ? "Greater" : "Lesser"
来一个完整的示例类,方便理解一些:
// 创建参数表达式using System.Reflection;
ParameterExpression xParameter = Expression.Parameter(typeof(int), "x");ParameterExpression yParameter = Expression.Parameter(typeof(int), "y");
// 创建常量表达式ConstantExpression constant = Expression.Constant(5);
// 创建二元表达式 x + yBinaryExpression add = Expression.Add(xParameter, yParameter);
// 创建一元表达式 -xUnaryExpression neg = Expression.Negate(xParameter);
// 创建方法调用表达式 Math.Sqrt(16.0)MethodInfo sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) });MethodCallExpression sqrtCall = Expression.Call(sqrtMethod, Expression.Constant(16.0));Expression<Func<double>> sqrtLambda = Expression.Lambda<Func<double>>(sqrtCall);Func<double> sqrtCompiled = sqrtLambda.Compile();double sqrtResult = sqrtCompiled();Console.WriteLine(sqrtResult); // 输出 4
// 创建条件表达式 x > 10 ? "Greater" : "Lesser"ConditionalExpression condition = Expression.Condition( Expression.GreaterThan(xParameter, Expression.Constant(10)), Expression.Constant("Greater"), Expression.Constant("Lesser"));
// 创建 Lambda 表达式 (x, y) => x + yExpression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(add, xParameter, yParameter);
// 编译并执行 Lambda 表达式Func<int, int, int> compiledLambda = lambda.Compile();int result = compiledLambda(3, 4);Console.WriteLine(result); // 输出 7
// 编译并执行条件表达式Expression<Func<int, string>> conditionLambda = Expression.Lambda<Func<int, string>>(condition, xParameter);Func<int, string> compiledCondition = conditionLambda.Compile();string conditionResult = compiledCondition(15);Console.WriteLine(conditionResult); // 输出 "Greater"
4、解析表达式树
这部分我用官方文档的示例逐步解析一下
// 创建一个表达式树Expression<Func<int, bool>> exprTree = num => num < 5;
//分解表达式树ParameterExpression param = exprTree.Parameters[0]; //提取第一个参数也是唯一一个参数名称 numConsole.WriteLine("参数名称: {0}", param.Name); //numBinaryExpression operation = (BinaryExpression)exprTree.Body;//返回表达式树的主体部分 num < 5ParameterExpression left = (ParameterExpression)operation.Left;//返回表达式的左操作数numConstantExpression right = (ConstantExpression)operation.Right;//返回表达式的右操作数,也就是5
Console.WriteLine("解析表达式: {0} => {1} {2} {3}", param.Name, left.Name, operation.NodeType, right.Value);//解析表达式: num => num LessThan 5
好的,示例里比较难理解的东西要来了,用一个递归算法来访问表达式节点
using System;using System.Linq.Expressions;
namespace Visitors;
// 基础 Visitor 类:public abstract class Visitor{ private readonly Expression node;
protected Visitor(Expression node) => this.node = node;
public abstract void Visit(string prefix);
public ExpressionType NodeType => node.NodeType; public static Visitor CreateFromExpression(Expression node) => node.NodeType switch { ExpressionType.Constant => new ConstantVisitor((ConstantExpression)node), ExpressionType.Lambda => new LambdaVisitor((LambdaExpression)node), ExpressionType.Parameter => new ParameterVisitor((ParameterExpression)node), ExpressionType.Add => new BinaryVisitor((BinaryExpression)node), _ => throw new NotImplementedException($"尚未处理的节点类型: {node.NodeType}"), };}
// Lambda Visitorpublic class LambdaVisitor : Visitor{ private readonly LambdaExpression node; public LambdaVisitor(LambdaExpression node) : base(node) => this.node = node;
public override void Visit(string prefix) { Console.WriteLine($"{prefix}这是一个 {NodeType} 表达式类型"); Console.WriteLine($"{prefix}Lambda 的名称是 {((node.Name == null) ? "<null>" : node.Name)}"); Console.WriteLine($"{prefix}返回类型是 {node.ReturnType}"); Console.WriteLine($"{prefix}表达式有 {node.Parameters.Count} 个参数。它们是:"); // 遍历每个参数: foreach (var argumentExpression in node.Parameters) { var argumentVisitor = CreateFromExpression(argumentExpression); argumentVisitor.Visit(prefix + "\t"); } Console.WriteLine($"{prefix}表达式主体是:"); // 遍历主体: var bodyVisitor = CreateFromExpression(node.Body); bodyVisitor.Visit(prefix + "\t"); }}
// 二元表达式 Visitor:public class BinaryVisitor : Visitor{ private readonly BinaryExpression node; public BinaryVisitor(BinaryExpression node) : base(node) => this.node = node;
public override void Visit(string prefix) { Console.WriteLine($"{prefix}这是一个 {NodeType} 二元表达式"); var left = CreateFromExpression(node.Left); Console.WriteLine($"{prefix}左操作数是:"); left.Visit(prefix + "\t"); var right = CreateFromExpression(node.Right); Console.WriteLine($"{prefix}右操作数是:"); right.Visit(prefix + "\t"); }}
// 参数 Visitor:public class ParameterVisitor : Visitor{ private readonly ParameterExpression node; public ParameterVisitor(ParameterExpression node) : base(node) { this.node = node; }
public override void Visit(string prefix) { Console.WriteLine($"{prefix}这是一个 {NodeType} 表达式类型"); Console.WriteLine($"{prefix}类型: {node.Type}, 名称: {node.Name}, ByRef: {node.IsByRef}"); }}
// 常量 Visitor:public class ConstantVisitor : Visitor{ private readonly ConstantExpression node; public ConstantVisitor(ConstantExpression node) : base(node) => this.node = node;
public override void Visit(string prefix) { Console.WriteLine($"{prefix}这是一个 {NodeType} 表达式类型"); Console.WriteLine($"{prefix}常量值的类型是 {node.Type}"); Console.WriteLine($"{prefix}常量值是 {node.Value}"); }}
using Visitors;
// 创建一个简单的表达式树Expression<Func<int, int, int>> addition = (a, b) => a + b;
// 使用 Visitor 类来遍历和打印表达式树var additionBody = Visitor.CreateFromExpression(addition);additionBody.Visit("");
// 这是一个 Lambda 表达式类型// Lambda 的名称是 <null>// 返回类型是 System.Int32// 表达式有 2 个参数。它们是:// 这是一个 Parameter 表达式类型// 类型: System.Int32, 名称: a, ByRef: False// 这是一个 Parameter 表达式类型// 类型: System.Int32, 名称: b, ByRef: False// 表达式主体是:// 这是一个 Add 二元表达式// 左操作数是:// 这是一个 Parameter 表达式类型// 类型: System.Int32, 名称: a, ByRef: False// 右操作数是:// 这是一个 Parameter 表达式类型// 类型: System.Int32, 名称: b, ByRef: False
这个示例的话,自己拿去debug看下流程会更好理解一些
5、表达式树到底有什么用呢
我先写两个例子
List<int> list = [1, 2, 3, 4, 5];// 创建一个表达式树来表示查询Expression<Func<int, bool>> expr = num => num > 3;
// 使用表达式树进行查询var result = list.AsQueryable().Where(expr).ToList();
// 输出结果result.ForEach(Console.WriteLine); // 输出 4, 5
这个时候就有问题了,我不用表达式树不也一样能用吗?我可以直接用委托实现
Func<int, bool> func = num => num > 3;
// 使用表达式树进行查询var result1 = list.Where(func).ToList();
// 输出结果result.ForEach(Console.WriteLine); // 输出 4, 5
怎么感觉表达式树能做的,我用其他方式也能做,那到底有什么用?就我个人来说,表达式树最大的作用就是把lambda表达式能够很准确的解析出来,然后转换成其他语言,最常用的就是orm通过表达式树解析成sql语句,来个简单的小示例
Expression<Func<Product, bool>> expression = p => p.Price > 15 && p.Name.Contains("Product")&&p.Price<=45;
var sql = ExpressionToSql.Translate(expression);Console.WriteLine(sql);//((Price > 15) AND Name LIKE 'Product')
public class Product{ public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; }}
public static class ExpressionToSql{ /// <summary> /// 将表达式树转换为SQL查询字符串。 /// </summary> /// <param name="expression">要转换的表达式树。</param> /// <returns>生成的SQL查询字符串。</returns> public static string Translate(Expression expression) { var visitor = new SqlExpressionVisitor(); visitor.Visit(expression); return visitor.GetSql(); }}
public class SqlExpressionVisitor : ExpressionVisitor{ private StringBuilder _sqlBuilder = new StringBuilder();
/// <summary> /// 获取生成的SQL查询字符串。 /// </summary> /// <returns>生成的SQL查询字符串。</returns> public string GetSql() { return _sqlBuilder.ToString(); }
/// <summary> /// 访问二元表达式节点(如 &&, ||, >, < 等) /// </summary> /// <param name="node">二元表达式节点</param> /// <returns>访问后的表达式节点</returns> protected override Expression VisitBinary(BinaryExpression node) { _sqlBuilder.Append("("); Visit(node.Left); // 访问左侧表达式 _sqlBuilder.Append($" {GetSqlOperator(node.NodeType)} "); // 添加SQL运算符 Visit(node.Right); // 访问右侧表达式 _sqlBuilder.Append(")"); return node; }
/// <summary> /// 访问常量表达式节点 /// </summary> /// <param name="node">常量表达式节点</param> /// <returns>访问后的表达式节点</returns> protected override Expression VisitConstant(ConstantExpression node) { if (node.Type == typeof(string)) { _sqlBuilder.Append($"'{node.Value}'"); // 字符串常量需要加引号 } else { _sqlBuilder.Append(node.Value); // 其他类型直接添加值 } return node; }
/// <summary> /// 访问成员表达式节点 /// </summary> /// <param name="node">成员表达式节点</param> /// <returns>访问后的表达式节点</returns> protected override Expression VisitMember(MemberExpression node) { _sqlBuilder.Append(node.Member.Name); // 添加成员名称(如属性名) return node; }
/// <summary> /// 访问方法调用表达式节点(如字符串的 Contains 方法) /// </summary> /// <param name="node">方法调用表达式节点</param> /// <returns>访问后的表达式节点。</returns> protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.Name == "Contains" && node.Object.Type == typeof(string)) { Visit(node.Object); // 访问对象(如字符串) _sqlBuilder.Append(" LIKE "); Visit(node.Arguments[0]); // 访问方法参数 } return node; }
/// <summary> /// 根据表达式类型获取对应的SQL运算符 /// </summary> /// <param name="nodeType">表达式类型</param> /// <returns>对应的SQL运算符</returns> private string GetSqlOperator(ExpressionType nodeType) { switch (nodeType) { case ExpressionType.AndAlso: return "AND"; case ExpressionType.OrElse: return "OR"; case ExpressionType.Equal: return "="; case ExpressionType.NotEqual: return "<>"; case ExpressionType.GreaterThan: return ">"; case ExpressionType.GreaterThanOrEqual: return ">="; case ExpressionType.LessThan: return "<"; case ExpressionType.LessThanOrEqual: return "<="; default: throw new NotSupportedException($"Unsupported node type: {nodeType}"); } }}
大致流程是 创建一个自定义的表达式树定义并且实现ExpressionVisitor
接口,重写自己想要自定义的表达式节点,这里是重写了常量表达式、二元表达式、成员表达式和方法调用表达式,表达式树结构如下:
BinaryExpression (&&)├── BinaryExpression (>)│ ├── MemberExpression (p.Price)│ └── ConstantExpression (15)└── BinaryExpression (&&) ├── MethodCallExpression (Contains) │ └── MemberExpression (p.Name) │ └── ConstantExpression ("Product") └── BinaryExpression (<=) ├── MemberExpression (p.Price) └── ConstantExpression (45)
引用
https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/expression-trees/