【C#表达式树 开篇】 Expression Tree - 动态语言

.NET 3.5中新增的表达式树(Expression Tree)特性,第一次在.NET平台中引入了“逻辑即数据”的概念。也就是说,我们可以在代码里使用高级语言的形式编写一段逻辑,但是这段逻辑最终会被保存为数据。正因为如此,我们可以使用各种不同的方法对它进行处理。例如,您可以将其转化为一个SQL查询,或者外部服务调用等等,这便是LINQ to Everything在技术实现上的重要基石之一。

学习表达式目录树的目的:

1、之所以学习表达式树的相关知识点主要是为了能进一步理解linq to sql相关执行原理

2、表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性。
3、表达式具有缓存性,可以用与替代反射

 

在学习表达式目录树前我们要回顾一下知识点:

【C# 基础概念】表达式(expression)、语句(statement)、块(block),指令(using)

任何表达式都有值和类型两个基本属性

 表达式的每个节点都是表达式,所以即使是常量都要封装成几点,然后添加的树中

进入正题。。。。。。。。。。。。

表达式树是什么?

表达式树是一个树结构,c#引入表达式树,用来储存动态代码,因此C#中的表达式树是一种数据结构,这个数据结构用来存储代码。

我们把它当成另外一种动态语言学习就好了,它也有常量、参数、运算、判断等操作。

 

图片

 

下一节我们介绍到expression类的时候就就会接触到它各种函数。

 表达式树是不可变对象(immutable),跟string类似,不能直接修改,只能复制一个然后重新构造。具体参考MSDN How to modify expression trees (C#).

表达式树的创建有 Lambda法 和 API组装法

 

表达式树 (C#)

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。

你可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。 有关 LINQ 中表达式树的详细信息,请参阅如何使用表达式树生成动态查询 (C#)

表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL)。 有关 DLR 的详细信息,请参阅动态语言运行时概述

你可以基于匿名 lambda 表达式通过 C# 或者 Visual Basic 编译器创建表达式树,或者通过 System.Linq.Expressions 名称空间手动创建。

表达式树的组成部分

  • Body: 得到表达式的主体。
  • Parameters: 得到lambda表达式的参数.
  • NodeType: 获取树的节点的类型(NodeType)是 ExpressionType。标注当前这个节点具体在干什么。共85种不同值,包含所有表达式节点各种可能的类型,例如返回常量,例如返回参数,例如取两个值的小值(<),例如取两个值的大值(>),例如将值相加(+),等等。
  • Type: 获取表达式的一个静态类型。在这个例子里,表达式的类型是Func<intintint>。

 

图片

 

Codex 图像 2026年9月21日 11_59_27

 

创建表达式树2种方式

1、根据 Lambda 表达式创建表达式树

若 lambda 表达式被分配给 Expression<TDelegate> 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。

下列代码示例展示如何通过 C# 编译器创建表示 Lambda 表达式 num => num < 5 的表达式树。

Expression<Func<int, bool>> lambda = num => num < 5;  

 

2、通过 API 创建表达式树

通过 API 创建表达式树需要使用 Expression 类。 类包含创建特定类型表达式树节点的静态工厂方法,比如表示参数变量的 ParameterExpression,或者是表示方法调用的 MethodCallExpressionParameterExpression 名称空间还解释了 MethodCallExpressionSystem.Linq.Expressions和另一种具体表达式类型。 这些类型来源于抽象类型 Expression

下列代码示例展示如何使用 API 创建表示 Lambda 表达式 num => num < 5 的表达式树。

// Add the following using directive to your code file:  
// using System.Linq.Expressions;  
  
// Manually build the expression tree for
// the lambda expression num => num < 5.  
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");  
ConstantExpression five = Expression.Constant(5, typeof(int));  
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);  
Expression<Func<int, bool>> lambda1 =  
    Expression.Lambda<Func<int, bool>>(  
        numLessThanFive,  
        new ParameterExpression[] { numParam });  

API的方式创建更复杂的表达式树

在 .NET Framework 4 或更高版本中,表达式树 API 还支持赋值表达式和控制流表达式,例如循环、条件块和 try-catch 块等。 相对于通过 C# 编译器和 Lambda 表达式创建表达式树,还可利用 API 创建更加复杂的表达式树。 下列示例展示如何创建计算数字阶乘的表达式树。

 

// Creating a parameter expression.  
ParameterExpression value = Expression.Parameter(typeof(int), "value");  
  
// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");  
  
// Creating a label to jump to from a loop.  
LabelTarget label = Expression.Label(typeof(int));  
  
// Creating a method body.  
BlockExpression block = Expression.Block(  
    // Adding a local variable.  
    new[] { result },  
    // Assigning a constant to a local variable: result = 1  
    Expression.Assign(result, Expression.Constant(1)),  
    // Adding a loop.  
        Expression.Loop(  
    // Adding a conditional block into the loop.  
           Expression.IfThenElse(  
    // Condition: value > 1  
               Expression.GreaterThan(value, Expression.Constant(1)),  
    // If true: result *= value --  
               Expression.MultiplyAssign(result,  
                   Expression.PostDecrementAssign(value)),  
    // If false, exit the loop and go to the label.  
               Expression.Break(label, result)  
           ),  
    // Label to jump to.  
       label  
    )  
);  
  
// Compile and execute an expression tree.  
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);  
  
Console.WriteLine(factorial);  
// Prints 120.

表达式树永久性

表达式树应具有永久性。 这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。 你可以使用表达式树访问者遍历现有表达式树。 有关详细信息,请参阅如何修改表达式树 (C#)

 

 

编译并且执行表达式树

C# 编译器只能从表达式 Lambda(或单行 Lambda)生成表达式树。 它无法解析语句 lambda (或多行 lambda)。 有关 C# 中 Lambda 表达式的详细信息,请参阅 Lambda 表达式

表达式树 编译后会生成委托

//创建表达式的语法糖 
Expression<Func<int, bool>> expr = num => num < 5;  
  
// Compiling the expression tree into a delegate.  
Func<int, bool> result = expr.Compile();  
  
// Invoking the delegate and writing the result to the console.  
Console.WriteLine(result(4));  

  //语法糖,编译后执行  
Console.WriteLine(expr.Compile()(4));  

注意:public Delegate Compile();
LambdaExpression.Compile() 方法返回 Delegate 类型。 必须将其转换为正确的委托类型,以便使任何编译时工具检查参数列表或返回类型。


public sealed class Expression<TDelegate>
{
public TDelegate Compile(){}
}
Expression<Func<int>> add = () => 1 + 2;
var func = add.Compile(); // Create Delegate
该委托类型基于表达式类型。 如果想要以强类型的方式使用委托对象,则必须知道返回类型和参数列表。

 

 

 

 

生存期

通过调用在调用 LambdaExpression.Compile() 时创建的委托来执行代码。 可以在上面进行查看,其中 add.Compile() 返回了一个委托。 通过调用 func() 调用该委托将执行代码。

该委托表示表达式树中的代码。 可以保留该委托的句柄并在稍后调用它。 不需要在每次想要执行表达式树所表示的代码时编译表达式树。 (请记住,表达式树是不可变的,且在之后编译同一表达式树将创建执行相同代码的委托。)

 

表达式树的访问者(ExpressionVisitor

ExpressionVisitor(表达式访问者)是 .NET 框架提供的一个专门用来遍历、分析或修改表达式树(Expression Tree)的基类

它的核心机制基于设计模式中的访问者模式(Visitor Pattern)

如果你把表达式树想象成一棵二叉树,那么 ExpressionVisitor 就是一个自带递归算法的“树型巡检员”

 为什么需要 ExpressionVisitor

因为表达式树是不可变的(Immutable)。一旦创建,你就不能直接修改树中的某个节点(比如你不能直接把 a + b 里的 + 变成 -,或者把参数 u 换成 p)。

如果你想修改一棵表达式树,唯一的办法就是:

  1. 从根节点开始递归遍历整棵树。

  2. 找到你想修改的节点,创建一个新节点代替它。

  3. 把不需要修改的节点原样复制。

  4. 最终拼装并返回一棵全新的表达式树。

手写递归遍历整棵树极其繁琐(因为节点类型有 85 种,如 BinaryExpressionMethodCallExpressionParameterExpression 等)。ExpressionVisitor 就是微软帮你写好的标准递归框架。

它怎么工作?(运行机制)

ExpressionVisitor 内部为每一种节点类型都准备了一个 VisitXxx 方法:

  • Visit(Expression node):入口方法,根据节点类型分发到具体的 Visit 方法。

  • VisitBinary(BinaryExpression node):处理二元运算(如 +, -, &&, ||)。

  • VisitParameter(ParameterExpression node):处理参数(如 u => 中的 u)。

  • VisitConstant(ConstantExpression node):处理常量(如数字 18,字符串 "Alice")。

  • VisitMethodCall(MethodCallExpression node):处理方法调用(如 .Contains())。

注意事项

注意闭包问题,要保证被lambda捕获的局部变量,在在表达式树生成的的委托 是都可用

https://docs.microsoft.com/zh-cn/dotnet/csharp/expression-trees-execution

 

 

与表达式树相关的类介绍

Expressions 表达式的命名空间

Expression 表达式抽象基类通常做继承

Expression<T>=>LambdaExpression=>Expression=>Object 用来接收LambdaExpression.ComPile 返回值。

ExpressionType:表达式树的节点类型枚举

 

在这里插入图片描述
当然还有没有直接继承Expression的,比如:Expression<T>(我们最常用的表达式树)的继承关系为:Expression<T>=>LambdaExpression=>Expression=>Object,这里我就挑选几个表达式树简单描述一下:

    BinaryExpression:二元运算表达式
      1). Right:二元运算符的右侧表达式(Expression)
      2). Left:二元运算符的左侧表达式(Expression)
    ConstantExpression:常量表达式
      1). Value:常量值,Object
    ConditionalExpression:条件表达式
      1). IfFalse:为False时的表达式(Expression)
      2). IfTrue:为True时的表达式(Expression)
      3). Test:判断表达式
    ParameterExpression:参数表达式
      1). IsByRef:参数是否传引用
      2). Name:参数名称
    LambdaExpression:lambda表达式
      1).Body:内容表达式(Expression)
      2).Parameters:参数表达式集合(ReadOnlyCollection)
      3).ReturnType:返回的类型
      4).Compile():方法,编译生成委托
    Expression<T>:带有泛型的表达式,一般这个T就是委托类型的,继承自LabelExpression

posted @ 2021-10-19 23:17  小林野夫  阅读(1178)  评论(0)    收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/