表达式树 是表示一些代码的数据结构。 它不是已编译且可执行的代码。 如果想要执行由表达式树表示的 .NET 代码,则必须将其转换为可执行的 IL 指令。

Lambda 表达式到函数

可以将任何 LambdaExpression 或派生自 LambdaExpression 的任何类型转换为可执行的 IL。 其他表达式类型不能直接转换为代码。 此限制在实践中影响不大。 Lambda 表达式是你可通过转换为可执行的中间语言 (IL) 来执行的唯一表达式类型。 (思考直接执行 ConstantExpression 意味着什么。 这是否意味着任何用处?)LambdaExpression 或派生自 LambdaExpression 的类型的任何表达式树均可转换为 IL。 表达式类型 Expression<TDelegate> 是 .NET Core 库中的唯一具体示例。 它用于表示映射到任何委托类型的表达式。 由于此类型映射到一个委托类型,因此 .NET 可以检查表达式,并为匹配 lambda 表达式签名的适当委托生成 IL。

在大多数情况下,这将在表达式和其对应的委托之间创建简单映射。 例如,由 Expression<Func<int>> 表示的表达式树将被转换为 Func<int> 类型的委托。 对于具有任何返回类型和参数列表的 Lambda 表达式,存在这样的委托类型:该类型是由该 Lambda 表达式表示的可执行代码的目标类型。

LambdaExpression 类型包含用于将表达式树转换为可执行代码的 Compile 和 CompileToMethod成员。 Compile 方法创建委托。 CompileToMethod 方法通过表示表达式树的已编译输出的 IL 更新 MethodBuilder 对象。 请注意,CompileToMethod 仅在完整的桌面框架中可用,不能用于 .NET Core。

还可以选择性地提供 DebugInfoGenerator,它将接收生成的委托对象的符号调试信息。 这让你可以将表达式树转换为委托对象,并拥有生成的委托的完整调试信息。

使用下面的代码将表达式转换为委托:

Expression<Func<int>> add = () =>  + ;
var func = add.Compile(); // 生产委托
var answer = func(); // 执行委托
Console.WriteLine(answer);

请注意,该委托类型基于表达式类型。 如果想要以强类型的方式使用委托对象,则必须知道返回类型和参数列表。 LambdaExpression.Compile() 方法返回 Delegate 类型。 必须将其转换为正确的委托类型,以便使任何编译时工具检查参数列表或返回类型。

执行和生存期

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

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

在此提醒你不要通过避免不必要的编译调用尝试创建用于提高性能的任何更复杂的缓存机制。 比较两个任意的表达式树,以确定如果它们表示相同的算法,是否也会花费很长的时间来执行。 你可能会发现,通过避免对 LambdaExpression.Compile() 的任何额外调用所节省的计算时间将多于执行代码(该代码确定可导致相同可执行代码的两个不同表达式树)所花费的时间。

注意事项

将 lambda 表达式编译为委托并调用该委托是可对表达式树执行的最简单的操作之一。 但是,即使是执行这个简单的操作,也存在一些必须注意的事项。

Lambda 表达式将对表达式中引用的任何局部变量创建闭包。 必须保证作为委托的一部分的任何变量在调用 Compile 的位置处和执行结果委托时可用。

一般情况下,编译器会确保这一点。 但是,如果表达式访问实现 IDisposable 的变量,则代码可能在表达式树仍保留有对象时释放该对象。

例如,此代码工作正常,因为 int 不实现 IDisposable

private static Func<int, int> CreateBoundFunc()
{
var constant = ; // 常量由表达式树捕获
Expression<Func<int, int>> expression = (b) => constant + b;
var rVal = expression.Compile();
return rVal;
}

委托已捕获对局部变量 constant 的引用。 在稍后执行 CreateBoundFunc 返回的函数之后,可随时访问该变量。

但是,请考虑实现 IDisposable 的此(人为设计的)类:

public class Resource : IDisposable
{
private bool isDisposed = false;
public int Argument
{
get
{
if (!isDisposed)
return ;
else throw new ObjectDisposedException("Resource");
}
} public void Dispose()
{
isDisposed = true;
}
}

如果将其用于如下所示的表达式中,则在执行 Resource.Argument 属性引用的代码时将出现 ObjectDisposedException

private static Func<int, int> CreateBoundResource()
{
using (var constant = new Resource()) // 常量由表达式树捕获
{
Expression<Func<int, int>> expression = (b) => constant.Argument + b;
var rVal = expression.Compile();
return rVal;
}
}

从此方法返回的委托已对释放了的 constant 对象闭包。 (它已被释放,因为它已在 using 语句中进行声明。)

现在,在执行从此方法返回的委托时,将在执行时引发 ObjectDisposedException

出现表示编译时构造的运行时错误确实很奇怪,但这是使用表达式树时的正常现象。

此问题存在大量的排列,因此很难提供用于避免此问题的一般性指导。 定义表达式时,请谨慎访问局部变量,且在创建可由公共 API 返回的表达式树时,谨慎访问当前对象(由 this 表示)中的状态。

表达式中的代码可能引用其他程序集中的方法或属性。 对表达式进行定义、编译或在调用结果委托时,该程序集必须可访问。 在它不存在的情况下,将遇到 ReferencedAssemblyNotFoundException

总结

可以编译表示 lambda 表达式的表达式树,以创建可执行的委托。 这提供了一种机制,用于执行表达式树所表示的代码。

表达式树表示会为创建的任意给定构造执行的代码。 只要编译和执行代码的环境匹配创建表达式的环境,则一切将按预期进行。 如果未按预期进行,那么错误也是很容易预知的,并且将在使用表达式树的任何代码的第一个测试中捕获这些错误。

C#3.0新增功能10 表达式树 04 执行表达式的更多相关文章

  1. C#3.0新增功能10 表达式树 05 解释表达式

    连载目录    [已更新最新开发文章,点击查看详细] 表达式树中的每个节点将是派生自 Expression 的类的对象. 该设计使得访问表达式树中的所有节点成为相对直接的递归操作. 常规策略是从根节点 ...

  2. C#3.0新增功能10 表达式树 07 翻译(转换)表达式

    连载目录    [已更新最新开发文章,点击查看详细] 本篇将介绍如何访问表达式树中的每个节点,同时生成该表达式树的已修改副本. 以下是在两个重要方案中将使用的技巧. 第一种是了解表达式树表示的算法,以 ...

  3. C#3.0新增功能10 表达式树 01 简介

    连载目录    [已更新最新开发文章,点击查看详细] 如果你使用过 LINQ,则会有丰富库(其中 Func 类型是 API 集的一部分)的经验. (如果尚不熟悉 LINQ,建议阅读 LINQ 教程,以 ...

  4. C#3.0新增功能10 表达式树 02 说明

    连载目录    [已更新最新开发文章,点击查看详细] 表达式树是定义代码的数据结构. 它们基于编译器用于分析代码和生成已编译输出的相同结构.表达式树和 Roslyn API 中用于生成分析器和 Cod ...

  5. C#3.0新增功能10 表达式树 03 支持表达式树的框架类型

    连载目录    [已更新最新开发文章,点击查看详细] 存在可与表达式树配合使用的 .NET Core framework 中的类的大型列表. 可以在 System.Linq.Expressions 查 ...

  6. C#3.0新增功能10 表达式树 06 生成表达式

    连载目录    [已更新最新开发文章,点击查看详细] 到目前为止,你所看到的所有表达式树都是由 C# 编译器创建的. 你所要做的是创建一个 lambda 表达式,将其分配给一个类型为 Expressi ...

  7. C#3.0新增功能09 LINQ 基础04 基本 LINQ 查询操作

    连载目录    [已更新最新开发文章,点击查看详细] 本篇介绍 LINQ 查询表达式和一些在查询中执行的典型操作. 获取数据源 在 LINQ 查询中,第一步是指定数据源. 和大多数编程语言相同,在使用 ...

  8. C#3.0新增功能08 Lambda 表达式

    连载目录    [已更新最新开发文章,点击查看详细] Lambda 表达式是作为对象处理的代码块(表达式或语句块). 它可作为参数传递给方法,也可通过方法调用返回. Lambda 表达式广泛用于: 将 ...

  9. C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点

    C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点   第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它 ...

随机推荐

  1. Windows下获取高精度时间注意事项 [转贴 AdamWu]

    花了很长时间才得到的经验,与大家分享. 1. RDTSC - 粒度: 纳秒级 不推荐优势: 几乎是能够获得最细粒度的计数器抛弃理由: A) 定义模糊 - 曾经据说是处理器的cycle counter, ...

  2. 凤年读史27:普鲁士vs德意志

    孙宇 普鲁士,是熟悉历史的人都知道的一个名词,它与德国关系密切.似乎在某些情境中,普鲁士就是德国,而在另一些场合,普鲁士和德国又不一样,有所区别.那么普鲁士到底是什么?它和德国到底是什么关系? 普鲁士 ...

  3. C# Task 的用法

    C# Task 的用法(转自:http://www.wxzzz.com/683.html#) 其实Task跟线程池ThreadPool的功能类似,不过写起来更为简单,直观.代码更简洁了,使用Task来 ...

  4. ORACLE(系统表student) 基本与深入学习

    (一).首先我们先创建student表(系统有的可以跳过往下看)没有直接复制运行即可. create table student(sno varchar2(3) not null, --学号sname ...

  5. Java 自定义异常(转载)

    1.异常的分类 1. 非运行时异常(Checked Exception) Java中凡是继承自Exception但不是继承自RuntimeException的类都是非运行时异常. 2. 运行时异常(R ...

  6. Selenium Grid分布式测试环境搭建

    Selenium Grid简介 Selenium Grid实际上是基于Selenium RC的,而所谓的分布式结构就是由一个hub节点和若干个node代理节点组成.Hub用来管理各个代理节点的注册信息 ...

  7. BZOJ 3990 排序

    [题目描述]: 小A有一个1~2N的排列A[1..2N],他希望将数组A从小到大排序.小A可以执行的操作有N种,每种操作最多可以执行一次.对于所有的i(1<=i<=N),第i种操作为:将序 ...

  8. 02(b)多元无约束优化问题-最速下降法

    此部分内容接02(a)多元无约束优化问题的内容! 第一类:最速下降法(Steepest descent method) \[f({{\mathbf{x}}_{k}}+\mathbf{\delta }) ...

  9. ZigBee按键中断

    何为按键中断? 在了解按键中断之前,我们先来了解一下什么是中断?中断就是程序执行当前代码,当前任务的时候: 突然有自身函数或外部的影响,而使程序执行到别的任务再回来. 举个栗子: 当你在做饭的时候,电 ...

  10. 对http请求进行过滤处理,转换成接收着需要的格式

    需要在Global.asax的Application中进行初始化处理 这样:GlobalConfiguration.Configuration.MessageHandlers.Add(new Defa ...