C#3.0新增功能10 表达式树 07 翻译(转换)表达式
本篇将介绍如何访问表达式树中的每个节点,同时生成该表达式树的已修改副本。 以下是在两个重要方案中将使用的技巧。 第一种是了解表达式树表示的算法,以便可以将其转换到另一个环境中。 第二种是何时更改已创建的算法。 这可能是为了添加日志记录、拦截方法调用并跟踪它们,或其他目的。
生成的用于转换表达式树的代码是你已看到的用于访问树中所有节点的代码的扩展。 转换表达式树时,会访问所有节点,并在访问它们的同时生成新树。 新树可包含对原始节点的引用或已放置在树中的新节点。
让我们通过访问表达式树,并创建具有一些替换节点的新树,来查看其工作原理。 在此示例中,我们将任何常数替换为其十倍大的常数。 我们通过将常数节点替换为执行乘法运算的新节点来进行此替换,而不必阅读常数的值并将其替换为新的常数。
此处,在找到常数节点后,创建新乘法节点(其子节点是原始常数和常数 10):
private static Expression ReplaceNodes(Expression original)
{
if (original.NodeType == ExpressionType.Constant)
{
return Expression.Multiply(original, Expression.Constant());
}
else if (original.NodeType == ExpressionType.Add)
{
var binaryExpression = (BinaryExpression)original;
return Expression.Add(ReplaceNodes(binaryExpression.Left),
ReplaceNodes(binaryExpression.Right));
}
return original;
}
通过替换原始节点,将形成一个包含修改的新树。 可以通过编译并执行替换的树对此进行验证。
var one = Expression.Constant(, typeof(int));
var two = Expression.Constant(, typeof(int));
var addition = Expression.Add(one, two);
var sum = ReplaceNodes(addition);
var executableFunc = Expression.Lambda(sum); var func = (Func<int>)executableFunc.Compile();
var answer = func();
Console.WriteLine(answer);
生成新树是两者的结合:访问现有树中的节点,和创建新节点并将其插入树中。
此示例演示了表达式树不可变这一点的重要性。 请注意,上面创建的新树混合了新创建的节点和现有树中的节点。 这是安全的,因为现有树中的节点无法进行修改。 这可以极大提高内存效率。 相同的节点可能会在整个树或多个表达式树中遍历使用。 由于不能修改节点,因此可以在需要时随时重用相同的节点。
var one = Expression.Constant(, typeof(int));
var two = Expression.Constant(, typeof(int));
var three= Expression.Constant(, typeof(int));
var four = Expression.Constant(, typeof(int));
var addition = Expression.Add(one, two);
var add2 = Expression.Add(three, four);
var sum = Expression.Add(addition, add2); // 声明委托,这样就可以从它本身递归地调用它
Func<Expression, int> aggregate = null;
// 聚合、返回常量或左、右操作数之和。
// 主要简化:假设每个二进制表达式都是一个加法。
aggregate = (exp) => exp.NodeType == ExpressionType.Constant
? (int)((ConstantExpression)exp).Value
: aggregate(((BinaryExpression)exp).Left) + aggregate(((BinaryExpression)exp).Right);
var theSum = aggregate(sum);
Console.WriteLine(theSum);
此处有相当多的代码,但这些概念是非常容易理解的。 此代码访问首次深度搜索后的子级。 当它遇到常数节点时,访问者将返回该常数的值。 访问者访问这两个子级之后,这些子级将计算出为该子树计算的总和。 加法节点现在可以计算其总和。 在访问了表达式树中的所有节点后,将计算出总和。 可以通过在调试器中运行示例并跟踪执行来跟踪执行。
让我们通过遍历树,来更轻松地跟踪如何分析节点以及如何计算总和。 下面是包含大量跟踪信息的聚合方法的更新版本:
private static int Aggregate(Expression exp)
{
if (exp.NodeType == ExpressionType.Constant)
{
var constantExp = (ConstantExpression)exp;
Console.Error.WriteLine($"Found Constant: {constantExp.Value}");
return (int)constantExp.Value;
}
else if (exp.NodeType == ExpressionType.Add)
{
var addExp = (BinaryExpression)exp;
Console.Error.WriteLine("Found Addition Expression");
Console.Error.WriteLine("Computing Left node");
var leftOperand = Aggregate(addExp.Left);
Console.Error.WriteLine($"Left is: {leftOperand}");
Console.Error.WriteLine("Computing Right node");
var rightOperand = Aggregate(addExp.Right);
Console.Error.WriteLine($"Right is: {rightOperand}");
var sum = leftOperand + rightOperand;
Console.Error.WriteLine($"Computed sum: {sum}");
return sum;
}
else throw new NotSupportedException("Haven't written this yet");
}
在同一表达式中运行该版本将生成以下输出:
Found Addition Expression
Computing Left node
Found Addition Expression
Computing Left node
Found Constant:
Left is:
Computing Right node
Found Constant:
Right is:
Computed sum:
Left is:
Computing Right node
Found Addition Expression
Computing Left node
Found Constant:
Left is:
Computing Right node
Found Constant:
Right is:
Computed sum:
Right is:
Computed sum:
跟踪输出,并在上面的代码中跟随。 应当能够看出代码如何在遍历树的同时访问代码和计算总和,并得出总和。
现在,让我们来看看另一个运行,其表达式由 sum1 给出:
Expression<Func<int> sum1 = () => + ( + ( + ));
下面是通过检查此表达式得到的输出:
Found Addition Expression
Computing Left node
Found Constant:
Left is:
Computing Right node
Found Addition Expression
Computing Left node
Found Constant:
Left is:
Computing Right node
Found Addition Expression
Computing Left node
Found Constant:
Left is:
Computing Right node
Found Constant:
Right is:
Computed sum:
Right is:
Computed sum:
Right is:
Computed sum:
虽然最终结果是相同的,但树遍历完全不同。 节点的访问顺序不同,因为树是以首先发生的不同运算构造的。
限制
存在一些不好翻译成表达式树的较新的 C# 语言元素。 表达式树不能包含 await 表达式或 async lambda 表达式。 C# 6 发行中添加的许多功能不会完全按照表达式树中所编写的那样显示。 较新的功能可能会显示在表达式树中等效、早期的语法中。 这可能不像你想象的那样有局限性。 实际上,这意味着在引入新语言功能时,解释表达式树的代码将仍可能照常运行。
即使具有这些限制,通过表达式树,仍可创建依赖于解释和修改表示为数据结构的代码的动态算法。 它是一种功能强大的工具,作为 .NET 生态系统的一种功能,它可使丰富的库(如实体框架)完成其所执行的操作。
C#3.0新增功能10 表达式树 07 翻译(转换)表达式的更多相关文章
- C#3.0新增功能10 表达式树 01 简介
连载目录 [已更新最新开发文章,点击查看详细] 如果你使用过 LINQ,则会有丰富库(其中 Func 类型是 API 集的一部分)的经验. (如果尚不熟悉 LINQ,建议阅读 LINQ 教程,以 ...
- C#3.0新增功能10 表达式树 06 生成表达式
连载目录 [已更新最新开发文章,点击查看详细] 到目前为止,你所看到的所有表达式树都是由 C# 编译器创建的. 你所要做的是创建一个 lambda 表达式,将其分配给一个类型为 Expressi ...
- C#3.0新增功能10 表达式树 05 解释表达式
连载目录 [已更新最新开发文章,点击查看详细] 表达式树中的每个节点将是派生自 Expression 的类的对象. 该设计使得访问表达式树中的所有节点成为相对直接的递归操作. 常规策略是从根节点 ...
- C#3.0新增功能10 表达式树 02 说明
连载目录 [已更新最新开发文章,点击查看详细] 表达式树是定义代码的数据结构. 它们基于编译器用于分析代码和生成已编译输出的相同结构.表达式树和 Roslyn API 中用于生成分析器和 Cod ...
- C#3.0新增功能10 表达式树 03 支持表达式树的框架类型
连载目录 [已更新最新开发文章,点击查看详细] 存在可与表达式树配合使用的 .NET Core framework 中的类的大型列表. 可以在 System.Linq.Expressions 查 ...
- C#3.0新增功能10 表达式树 04 执行表达式
连载目录 [已更新最新开发文章,点击查看详细] 表达式树 是表示一些代码的数据结构. 它不是已编译且可执行的代码. 如果想要执行由表达式树表示的 .NET 代码,则必须将其转换为可执行的 IL ...
- C#3.0新增功能09 LINQ 基础07 LINQ 中的查询语法和方法语法
连载目录 [已更新最新开发文章,点击查看详细] 介绍性的语言集成查询 (LINQ) 文档中的大多数查询是使用 LINQ 声明性查询语法编写的.但是在编译代码时,查询语法必须转换为针对 .NET ...
- C#3.0新增功能08 Lambda 表达式
连载目录 [已更新最新开发文章,点击查看详细] Lambda 表达式是作为对象处理的代码块(表达式或语句块). 它可作为参数传递给方法,也可通过方法调用返回. Lambda 表达式广泛用于: 将 ...
- C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点
C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点 第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它 ...
随机推荐
- Virtualbox使用点滴(共享USB设备,Linux下我的用户没有加到vboxuser中去)
由于网银客户端的问题,只能够在windows环境下支付,所以一直保存着一个激活的virtualbox下的windows,用来完成在线支付. 过去这个激活的windows是安装在ubuntu 10.10 ...
- 判断一个窗口是否被挂起(发WM_NULL消息,或者调用IsHungAppWindow API进行测试)
判断一个窗口是否被挂起了(就是没有响应了),在多窗口编程了经常会用到,在给别的窗口发消息前,为了目的窗口能确定收到消息,常常在之前先检测窗口是否被挂起了,我们以前常用的方式的是使用下面的方法: // ...
- IIS上.net注册
如果先安装了.Net平台,后再安装IIS,那么在IIS中可能就没有出现ASP.NET版本的下拉菜单,这是我们可手动注册.Net 一般.Net版本都存放在:C:\WINDOWS\Microsoft.NE ...
- QList使用下标[index]才可以获得可修改的item的引用(估计QStringList也是如此)
QList算是最常用的集合了,今儿偶然间需要修改QList中的值,结果郁闷了.QList中提供了replace函数来替换item,但不是修改.而at().value()操作均返回的是const的ite ...
- 大数据基础之Kafka(1)简介、安装及使用
kafka2.0 http://kafka.apache.org 一 简介 Kafka® is used for building real-time data pipelines and strea ...
- Java逆序输出整数
题目要求:编写方法reverseDigit,将一个整数作为参数,并反向返回该数字.例如reverseDigit(123)的值是321.同时编写程序测试此方法. 说明:10的倍数的逆序,均以实际结果为准 ...
- lower_bound 和 upper_bound 功能和用法
以前用这两个函数的时候,简单看了几句别人的博客,记住了大概,用的时候每用一次就弄混一次,相当难受,今天对照着这两个函数的源码和自己的尝试发现:其实这两个函数只能用于 "升序" 序列 ...
- vs 编译说明
静态编译/MT,/MTD 是指使用libc和msvc相关的静态库(lib). 动态编译,/MD,/MDd是指用相应的DLL版本编译. 其中字母含义 d:debug m:multi-th ...
- .gitignore 的简单实用
a: 在工作目录下右键点击使用git命令行(GitBash) b: 输入 touch .gitignore 命令,此时会在工作目录下生成一个“.gitignore”的文本文件 注:touch在git里 ...
- java基础第十八篇之单元测试、注解和动态代理
1:单元测试 1)JUnit是一个Java语言的单元测试框架,这里的单元指的就是方法 2)单元测试用来替换以前的main方法 1.1 Junit测试的步骤 1:在方法的上面加上 @Test 2:将ju ...