到目前为止,你所看到的所有表达式树都是由 C# 编译器创建的。 你所要做的是创建一个 lambda 表达式,将其分配给一个类型为 Expression<Func<T>> 或某种相似类型的变量。 这不是创建表达式树的唯一方法。 很多情况下,可能需要在运行时在内存中生成一个表达式。

由于这些表达式树是不可变的,所以生成表达式树很复杂。 不可变意味着必须以从叶到根的方式生成表达式树。 用于生成表达式树的 API 体现了这一点:用于生成节点的方法将其所有子级用作参数。 让我们通过几个示例来了解相关技巧。

创建节点
从相对简单的内容开始。 我们将使用在这些部分中一直使用的加法表达式:
Expression<Func<int>> sum = () =>  + ;

若要构造该表达式树,必须构造叶节点。 叶节点是常量,因此可以使用 Expression.Constant 方法创建节点:

var one = Expression.Constant(, typeof(int));
var two = Expression.Constant(, typeof(int));

接下来,将生成加法表达式:

var addition = Expression.Add(one, two);

一旦获得了加法表达式,就可以创建 lambda 表达式:

var lambda = Expression.Lambda(addition);

这是一个非常简单的 Lambda 表达式,因为它不包含任何参数。 在本节的后续部分,你将了解如何将实参映射到形参并生成更复杂的表达式。

对于此类简单的表达式,可以将所有调用合并到单个语句中:

var lambda = Expression.Lambda(
Expression.Add(Expression.Constant(, typeof(int)),
Expression.Constant(, typeof(int))
)
);
生成树

这是在内存中生成表达式树的基础知识。 更复杂的树通常意味着更多的节点类型,并且树中有更多的节点。 让我们再浏览一个示例,了解通常在创建表达式树时创建的其他两个节点类型:参数节点和方法调用节点。

生成一个表达式树以创建此表达式:

Expression<Func<double, double, double>> distanceCalc = (x, y) => Math.Sqrt(x * x + y * y);

首先,创建 x 和 y 的参数表达式:

var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");

按照你所看到的模式创建乘法和加法表达式:

var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);

接下来,需要为调用 Math.Sqrt 创建方法调用表达式。

var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) });
var distance = Expression.Call(sqrtMethod, sum);

最后,将方法调用放入 lambda 表达式,并确保定义 lambda 表达式的参数:

var distanceLambda = Expression.Lambda(distance,xParameter, yParameter);

在这个更复杂的示例中,你看到了创建表达式树通常使用的其他几种技巧。

首先,在使用它们之前,需要创建表示参数或局部变量的对象。 创建这些对象后,可以在表达式树中任何需要的位置使用它们。

其次,需要使用反射 API 的一个子集来创建 MethodInfo 对象,以便创建表达式树以访问该方法。 必须仅限于 .NET Core 平台上提供的反射 API 的子集。 同样,这些技术将扩展到其他表达式树。

深度生成代码

不仅限于使用这些 API 可以生成的代码。 但是,要生成的表达式树越复杂,代码就越难以管理和阅读。

让我们生成一个与此代码等效的表达式树:

Func<int, int> factorialFunc = (n) =>
{
var res = ;
while (n > )
{
res = res * n;
n--;
}
return res;
};

请注意上面我未生成表达式树,只是生成了委托。 使用 Expression 类不能生成语句 lambda。 下面是生成相同的功能所需的代码。 它很复杂,这是因为没有用于生成 while循环的 API,而是需要生成一个包含条件测试的循环和一个用于中断循环的标签目标。

 var nArgument = Expression.Parameter(typeof(int), "n");
var result = Expression.Variable(typeof(int), "result"); // 创建一个表示返回值的标签
LabelTarget label = Expression.Label(typeof(int)); var initializeResult = Expression.Assign(result, Expression.Constant()); // 这是执行乘法运算的内部块,
// 并减小“n”的值
var block = Expression.Block(
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
); // 创建一个方法体
BlockExpression body = Expression.Block(
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant()),
block,
Expression.Break(label, result)
),
label
)
);
检查 API

表达式树 API 在 .NET Core 中较难导航,但没关系。 它们的用途相当复杂:编写在运行时生成代码的代码。 它们必须具有复杂的结构,才能在支持 C# 语言中提供的所有控件结构和尽可能减小 API 表面积之间保持平衡。 这种平衡意味着许多控件结构不是由其 C# 构造表示,而是由表示基础逻辑的构造表示,这些基础逻辑由编译器从这些较高级别的构造生成。

另外,此时存在一些不能通过使用 Expression 类方法直接生成的 C# 表达式。 一般来说,这些将是在 C# 5 和 C# 6 中添加的最新运算符和表达式。 (例如,无法生成 async 表达式,并且无法直接创建新 ?. 运算符。)

C#3.0新增功能10 表达式树 06 生成表达式的更多相关文章

  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 表达式树 04 执行表达式

    连载目录    [已更新最新开发文章,点击查看详细] 表达式树 是表示一些代码的数据结构. 它不是已编译且可执行的代码. 如果想要执行由表达式树表示的 .NET 代码,则必须将其转换为可执行的 IL ...

  7. C#3.0新增功能09 LINQ 基础06 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. 浅谈网络I/O多路复用模型 select & poll & epoll

    http://blog.csdn.net/nk_test/article/details/50662946

  2. 爬取虎扑NBA首页主干道推荐贴的一只小爬虫,日常爬不冷笑话解闷

    虎扑是广大jrs的家园,步行街是这个家园里最繁华的地段.据称广大jrs平均学历985,步行街街薪30w起步. 大学时经舍友安利,开始了解虎扑,主要是看看NBA的一些资讯. 偶尔也上上这个破街,看看jr ...

  3. Spring5源码深度分析(二)之理解@Conditional,@Import注解

    代码地址: 1.源码分析二主要分析的内容 1.使用@Condition多条件注册bean对象2.@Import注解快速注入第三方bean对象3.@EnableXXXX 开启原理4.基于ImportBe ...

  4. epoll模型的探索与实践

    我们知道nginx的效率非常高,能处理上万级的并发,其之所以高效离不开epoll的支持, epoll是什么呢?,epoll是IO模型中的一种,属于多路复用IO模型; 到这里你应该想到了,select, ...

  5. 阅读HashMap——jdk7时遇到的问题记录

    今天看hashMapJDK7版,水很深,看的也痛苦~推一个博主:http://www.cnblogs.com/xrq730/ 看了源码,才发现自己基础真的很薄弱,虽然当初也是下了很多的经历,但是毕竟大 ...

  6. Spring Environment抽象

    1:概述 Spring中Environment是Spring3.1版本引入的,是Spring核心框架定义的一个接口,用来表示整个应用运行时环境.该环境模型只接受两种应用环境profiles(配置文件) ...

  7. Django框架rest_framework中APIView的as_view()源码解析、认证、权限、频率控制

    在上篇我们对Django原生View源码进行了局部解析:https://www.cnblogs.com/dongxixi/p/11130976.html 在前后端分离项目中前面我们也提到了各种认证需要 ...

  8. 前端摸爬滚打之路(一)之 JavaScript 基础

    前言:这是我第一次在博客上记录自己的前端学习过程,以往都是在桌面右侧开个 onenote 小窗,记录自己在学习过程中获得的知识.通常都是记录的满满当当,然后心满意足的关闭窗口,但是记录不代表学会.这些 ...

  9. leadcode的Hot100系列--617. 合并二叉树

    合并,就是两个树的结构交集部分,数据相加,否则,取非空部分. 所以,这里相当于是对两棵树同时遍历: 如果两棵树节点都不为空,则数据相加, 否则,直接指针把不为空的节点复制过来. 注:这里没有申请内存, ...

  10. 机器学习读书笔记(五)AdaBoost

    一.Boosting算法 .Boosting算法是一种把若干个分类器整合为一个分类器的方法,在boosting算法产生之前,还出现过两种比较重要的将多个分类器整合为一个分类器的方法,即boostrap ...