表达式树 表示类似树的数据结构中的代码,其中每个节点都是表达式,例如方法调用或二进制作,例如 x < y
。
如果你使用过 LINQ,那么你就有使用丰富库的经验,而其中的 Func
类型是 API 集的一部分。 (如果你不熟悉 LINQ,你可能想要阅读 LINQ 教程 和有关此教程之前 lambda 表达式 的文章。表达式树提供与函数参数的更丰富的交互。
创建 LINQ 查询时,通常使用 Lambda 表达式编写函数参数。 在典型的 LINQ 查询中,这些函数参数将转换为编译器创建的委托。
你已经编写了使用表达式树的代码。 Entity Framework 的 LINQ API 接受表达式树作为 LINQ 查询表达式模式的参数。 这样 ,Entity Framework 就可以将用 C# 编写的查询转换为在数据库引擎中执行的 SQL。 另一个示例是 Moq,它是适用于 .NET 的常用模拟框架。
想要进行更丰富的交互时,需要使用 表达式树。 表达式树将代码表示为检查、修改或执行的结构。 这些工具赋予你在运行时控制代码的能力。 编写代码来检查正在运行的算法,或注入新功能。 在更高级的方案中,可以修改正在运行的算法,甚至将 C# 表达式转换为另一种窗体,以便在另一个环境中执行。
编译并运行由表达式树表示的代码。 生成和运行表达式树可以动态修改可执行代码、在各种数据库中执行 LINQ 查询以及创建动态查询。 有关 LINQ 中的表达式树的详细信息,请参阅 如何使用表达式树生成动态查询。
表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (CIL)。 有关 DLR 的详细信息,请参阅动态语言运行时概述。
可以让 C# 或 Visual Basic 编译器基于匿名 lambda 表达式为你创建表达式树,也可以使用命名空间手动 System.Linq.Expressions 创建表达式树。
将 lambda 表达式分配给类型的 Expression<TDelegate>变量时,编译器会发出代码来生成表示 lambda 表达式的表达式树。
下面的代码示例演示如何让 C# 编译器创建一个表示 lambda 表达式 num => num < 5
的表达式树。
Expression<Func<int, bool>> lambda = num => num < 5;
在代码中创建表达式树。 通过创建每个节点并将节点附加到树结构来生成树。 了解如何在 生成表达式树的文章中创建表达式。
表达式树是不可变的。 如果要修改表达式树,则必须通过复制现有表达式树并替换其中的节点来构造新的表达式树。 你可以使用表达式树访问者遍历现有表达式树。 有关详细信息,请参阅有关 翻译表达式树的文章。
生成表达式树后,将 执行由表达式树表示的代码。
局限性
C# 编译器只能从表达式 Lambda(或单行 Lambda)生成表达式树。 它无法解析语句 lambda (或多行 lambda)。 有关 C# 中的 lambda 表达式的详细信息,请参阅 Lambda 表达式。
有一些较新的 C# 语言元素无法很好地转换为表达式树。 表达式树不能包含 await
表达式或 async
lambda 表达式。 C# 6 和更高版本中添加的许多功能不会完全如表达式树中所示。 相反,较新的功能在表达式树中以等效的早期语法(如有可能)公开。 其他构造不可用。 这意味着当引入新的语言功能时,解释表达式树的代码的工作方式相同。 但是,即使存在这些限制,表达式树也能创建依赖于解释和修改表示为数据结构的代码的动态算法。 它使丰富的库(如 Entity Framework)能够完成其工作。
表达式树不支持新的表达式节点类型。 对于解释表达式树的所有库来说,这将是一项重大更改,用于引入新的节点类型。 以下列表包含大多数不能使用的 C# 语言元素:
- 从输出中删除的条件方法
-
base
访问 - 方法组表达式,包括方法组的
&
和匿名方法表达式 - 对本地函数的引用
- 语句,包括赋值 (
=
) 和语句形式表达式 - 仅具有定义声明的分部方法
- 不安全的指针操作
-
dynamic
操作 -
左侧为
null
或default
字面量的合并运算符、空合并赋值以及空传播运算符 (?.
) - 多维数组初始值设定项、 索引属性和字典初始值设定项
- 集合表达式
-
throw
表达 式 - 访问
static virtual
或abstract
接口成员 - 具有属性的 Lambda 表达式
- 内插字符串
- UTF-8 字符串转换或 UTF-8 字符串文本
- 使用变量参数、命名参数或可选参数的方法调用
- 使用 System.Index 或 System.Range 的表达式、“从末尾开始”索引 (
^
) 运算符或范围表达式 (..
) -
async
lambda 表达式或await
表达式,包括await foreach
和await using
-
元组文本、元组转换、元组
==
或!=
、with
表达式 -
弃元 (
_
)、析构赋值、模式匹配is
运算符或模式匹配switch
表达式 - COM 调用,参数中
ref
被省略 -
ref
、in
out
参数、ref
返回值、out
参数或ref struct
类型的任何值