表达式树中的每个节点将是派生自 Expression 的类的对象。

该设计使得访问表达式树中的所有节点成为相对直接的递归操作。 常规策略是从根节点开始并确定它是哪种节点。

如果节点类型具有子级,则以递归方式访问该子级。 在每个子节点中,重复在根节点处使用的步骤:确定类型,且如果该类型具有子级,则访问每个子级。

检查不具有子级的表达式
让我们首先访问一个非常简单的表达式树中的每个节点。 下面是创建常数表达式然后检查其属性的代码:
var constant = Expression.Constant(, typeof(int));

Console.WriteLine($"This is a/an {constant.NodeType} expression type");
Console.WriteLine($"The type of the constant value is {constant.Type}");
Console.WriteLine($"The value of the constant value is {constant.Value}");
将打印以下内容:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is
检查一个简单的加法表达式
从本节简介处的加法示例开始。
Expression<Func<int>> sum = () =>  + ;

没有使用 var 来声明此表达式树,因为此操作无法执行,这是由于赋值右侧是隐式类型而导致的。
不能使用隐式类型化变量声明来声明 lambda 表达式。 它会对编译器造成循环逻辑问题。 var 声明会告知编译器通过赋值运算符右侧的表达式的类型查明变量的类型。 Lambda 表达式没有编译时类型,但是可转换为任何匹配委托或表达式类型。 将 lambda 表达式分配给委托或表达式类型的变量时,可告知编译器尝试并将 lambda 表达式转换为与“分配对象”变量的签名匹配的表达式或委托。 编译器必须尝试使赋值右侧的内容与赋值左侧的类型匹配。
赋值两侧都无法告知编译器查看赋值运算符另一侧的对象并查看我的类型是否匹配。

根节点是 LambdaExpression。 为了获得 => 运算符右侧的有用代码,需要找到 LambdaExpression 的子级之一。 我们将通过本部分中的所有表达式来实现此目的。 父节点确实有助于找到 LambdaExpression 的返回类型。

若要检查此表达式中的每个节点,将需要以递归方式访问大量节点。 下面是一个简单的首次实现:

 Expression<Func<int, int, int>> addition = (a, b) => a + b;

 Console.WriteLine($"This expression is a {addition.NodeType} expression type");
Console.WriteLine($"The name of the lambda is {((addition.Name == null) ? "<null>" : addition.Name)}");
Console.WriteLine($"The return type is {addition.ReturnType.ToString()}");
Console.WriteLine($"The expression has {addition.Parameters.Count} arguments. They are:");
foreach(var argumentExpression in addition.Parameters)
{
Console.WriteLine($"\tParameter Type: {argumentExpression.Type.ToString()}, Name: {argumentExpression.Name}");
} var additionBody = (BinaryExpression)addition.Body;
Console.WriteLine($"The body is a {additionBody.NodeType} expression");
Console.WriteLine($"The left side is a {additionBody.Left.NodeType} expression");
var left = (ParameterExpression)additionBody.Left;
Console.WriteLine($"\tParameter Type: {left.Type.ToString()}, Name: {left.Name}");
Console.WriteLine($"The right side is a {additionBody.Right.NodeType} expression");
var right= (ParameterExpression)additionBody.Right;
Console.WriteLine($"\tParameter Type: {right.Type.ToString()}, Name: {right.Name}");

此示例打印以下输出:

This expression is a Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has arguments. They are:
Parameter Type: System.Int32, Name: a
Parameter Type: System.Int32, Name: b
The body is a Add expression
The left side is a Parameter expression
Parameter Type: System.Int32, Name: a
The right side is a Parameter expression
Parameter Type: System.Int32, Name: b

以上代码示例中中包含大量重复。 为了将其其清理干净,并生成一个更加通用的表达式节点访问者。 这将要求编写递归算法。 任何节点都可能是具有子级的类型。 具有子级的任何节点都要求访问这些子级并确定该节点是什么。 下面是利用递归访问加法运算的已优化的版本:

 // Visitor 基类:
public abstract class Visitor
{
private readonly Expression node; protected Visitor(Expression node)
{
this.node = node;
} public abstract void Visit(string prefix); public ExpressionType NodeType => this.node.NodeType;
public static Visitor CreateFromExpression(Expression node)
{
switch(node.NodeType)
{
case ExpressionType.Constant:
return new ConstantVisitor((ConstantExpression)node);
case ExpressionType.Lambda:
return new LambdaVisitor((LambdaExpression)node);
case ExpressionType.Parameter:
return new ParameterVisitor((ParameterExpression)node);
case ExpressionType.Add:
return new BinaryVisitor((BinaryExpression)node);
default:
Console.Error.WriteLine($"Node not processed yet: {node.NodeType}");
return default(Visitor);
}
}
} // Lambda Visitor
public class LambdaVisitor : Visitor
{
private readonly LambdaExpression node;
public LambdaVisitor(LambdaExpression node) : base(node)
{
this.node = node;
} public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression type");
Console.WriteLine($"{prefix}The name of the lambda is {((node.Name == null) ? "<null>" : node.Name)}");
Console.WriteLine($"{prefix}The return type is {node.ReturnType.ToString()}");
Console.WriteLine($"{prefix}The expression has {node.Parameters.Count} argument(s). They are:");
// Visit each parameter:
foreach (var argumentExpression in node.Parameters)
{
var argumentVisitor = Visitor.CreateFromExpression(argumentExpression);
argumentVisitor.Visit(prefix + "\t");
}
Console.WriteLine($"{prefix}The expression body is:");
// Visit the body:
var bodyVisitor = Visitor.CreateFromExpression(node.Body);
bodyVisitor.Visit(prefix + "\t");
}
} // 二元运算 Visitor:
public class BinaryVisitor : Visitor
{
private readonly BinaryExpression node;
public BinaryVisitor(BinaryExpression node) : base(node)
{
this.node = node;
} public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This binary expression is a {NodeType} expression");
var left = Visitor.CreateFromExpression(node.Left);
Console.WriteLine($"{prefix}The Left argument is:");
left.Visit(prefix + "\t");
var right = Visitor.CreateFromExpression(node.Right);
Console.WriteLine($"{prefix}The Right argument is:");
right.Visit(prefix + "\t");
}
} // 参数 visitor:
public class ParameterVisitor : Visitor
{
private readonly ParameterExpression node;
public ParameterVisitor(ParameterExpression node) : base(node)
{
this.node = node;
} public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}Type: {node.Type.ToString()}, Name: {node.Name}, ByRef: {node.IsByRef}");
}
} // 常量 visitor:
public class ConstantVisitor : Visitor
{
private readonly ConstantExpression node;
public ConstantVisitor(ConstantExpression node) : base(node)
{
this.node = node;
} public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This is an {NodeType} expression type");
Console.WriteLine($"{prefix}The type of the constant value is {node.Type}");
Console.WriteLine($"{prefix}The value of the constant value is {node.Value}");
}
}

此算法是可以访问任意 LambdaExpression 的算法的基础。 其中有大量缺口,即表明我创建的代码仅查找它可能遇到的表达式树节点组的一小部分。 但是,你仍可以从其结果中获益匪浅。 (遇到新的节点类型时,Visitor.CreateFromExpression 方法中的默认 case 会将消息打印到错误控制台。 如此,你便知道要添加新的表达式类型。)

在上面所示的加法表达式中运行此访问者时,将获得以下输出:

This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
检查具有许多级别的加法表达式
尝试更复杂的示例,但仍限制节点类型仅为加法:
Expression<Func<int>> sum = () =>  +  +  + ;

在访问者算法上运行此表达式之前,请尝试思考可能的输出是什么。 请记住,+ 运算符是二元运算符:它必须具有两个子级,分别表示左右操作数。 有几种可行的方法来构造可能正确的树:

Expression<Func<int>> sum1 = () =>  + ( + ( + ));
Expression<Func<int>> sum2 = () => (( + ) + ) + ; Expression<Func<int>> sum3 = () => ( + ) + ( + );
Expression<Func<int>> sum4 = () => + (( + ) + );
Expression<Func<int>> sum5 = () => ( + ( + )) + ;

可以看到可能的答案分为两种,以便着重于最有可能正确的答案。 第一种表示右结合表达式。 第二种表示左结合表达式。 这两种格式的优点是,格式可以缩放为任意数量的加法表达式。

如果确实通过该访问者运行此表达式,则将看到此输出,其验证简单的加法表达式是否为左结合

为了运行此示例并查看完整的表达式树,我不得不对源表达式树进行一次更改。 当表达式树包含所有常量时,所得到的树仅包含 10 的常量值。 编译器执行所有加法运算,并将表达式缩减为其最简单的形式。 只需在表达式中添加一个变量即可看到原始的树:

Expression<Func<int, int>> sum = (a) =>  + a +  + ;

创建可得出此总和的访问者并运行该访问者,则会看到以下输出:

 This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is

还可以通过访问者代码运行任何其他示例,并查看其表示的树。 下面是上述 sum3 表达式(使用附加参数来阻止编译器计算常量)的一个示例:

Expression<Func<int, int, int>> sum3 = (a, b) => ( + a) + ( + b);

下面是访问者的输出:

 This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: b, ByRef: False

请注意,括号不是输出的一部分。 表达式树中不存在表示输入表达式中的括号的节点。 表达式树的结构包含传达优先级所需的所有信息。

从此示例扩展
此示例仅处理最基本的表达式树。 在本部分中看到的代码仅处理常量整数和二进制 + 运算符。 作为最后一个示例,让我们更新访问者以处理更加复杂的表达式。 让我们这样来改进它:
Expression<Func<int, int>> factorial = (n) =>
n ==
?
: Enumerable.Range(, n).Aggregate((product, factor) => product * factor);

此代码表示数学 阶乘 函数的一个可能的实现。 编写此代码的方式强调了通过将 lambda 表达式分配到表达式来生成表达式树的两个限制。 首先,lambda 语句是不允许的。 这意味着无法使用循环、块、if / else 语句和 C# 中常用的其他控件结构。 我只能使用表达式。 其次,不能以递归方式调用同一表达式。 如果该表达式已是一个委托,则可以通过递归方式进行调用,但不能在其表达式树的形式中调用它。 在有关生成表达式树的部分中将介绍克服这些限制的技巧。

在此表达式中,将遇到所有这些类型的节点:

  1. Equal(二进制表达式)
  2. Multiply(二进制表达式)
  3. Conditional(? : 表达式)
  4. 方法调用表达式(调用 Range() 和 Aggregate()

修改访问者算法的其中一个方法是持续执行它,并在每次到达 default 子句时编写节点类型。 经过几次迭代之后,便将看到每个可能的节点。 这样便万事俱备了。 结果类似于:

 public static Visitor CreateFromExpression(Expression node)
{
switch(node.NodeType)
{
case ExpressionType.Constant:
return new ConstantVisitor((ConstantExpression)node);
case ExpressionType.Lambda:
return new LambdaVisitor((LambdaExpression)node);
case ExpressionType.Parameter:
return new ParameterVisitor((ParameterExpression)node);
case ExpressionType.Add:
case ExpressionType.Equal:
case ExpressionType.Multiply:
return new BinaryVisitor((BinaryExpression)node);
case ExpressionType.Conditional:
return new ConditionalVisitor((ConditionalExpression)node);
case ExpressionType.Call:
return new MethodCallVisitor((MethodCallExpression)node);
default:
Console.Error.WriteLine($"Node not processed yet: {node.NodeType}");
return default(Visitor);
}
}

ConditionalVisitor 和 MethodCallVisitor 将处理这两个节点:

 public class ConditionalVisitor : Visitor
{
private readonly ConditionalExpression node;
public ConditionalVisitor(ConditionalExpression node) : base(node)
{
this.node = node;
} public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression");
var testVisitor = Visitor.CreateFromExpression(node.Test);
Console.WriteLine($"{prefix}The Test for this expression is:");
testVisitor.Visit(prefix + "\t");
var trueVisitor = Visitor.CreateFromExpression(node.IfTrue);
Console.WriteLine($"{prefix}The True clause for this expression is:");
trueVisitor.Visit(prefix + "\t");
var falseVisitor = Visitor.CreateFromExpression(node.IfFalse);
Console.WriteLine($"{prefix}The False clause for this expression is:");
falseVisitor.Visit(prefix + "\t");
}
} public class MethodCallVisitor : Visitor
{
private readonly MethodCallExpression node;
public MethodCallVisitor(MethodCallExpression node) : base(node)
{
this.node = node;
} public override void Visit(string prefix)
{
Console.WriteLine($"{prefix}This expression is a {NodeType} expression");
if (node.Object == null)
Console.WriteLine($"{prefix}This is a static method call");
else
{
Console.WriteLine($"{prefix}The receiver (this) is:");
var receiverVisitor = Visitor.CreateFromExpression(node.Object);
receiverVisitor.Visit(prefix + "\t");
} var methodInfo = node.Method;
Console.WriteLine($"{prefix}The method name is {methodInfo.DeclaringType}.{methodInfo.Name}");
// There is more here, like generic arguments, and so on.
Console.WriteLine($"{prefix}The Arguments are:");
foreach(var arg in node.Arguments)
{
var argVisitor = Visitor.CreateFromExpression(arg);
argVisitor.Visit(prefix + "\t");
}
}
}

且表达式树的输出为:

 This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
The expression body is:
This expression is a Conditional expression
The Test for this expression is:
This binary expression is a Equal expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is
The True clause for this expression is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is
The False clause for this expression is:
This expression is a Call expression
This is a static method call
The method name is System.Linq.Enumerable.Aggregate
The Arguments are:
This expression is a Call expression
This is a static method call
The method name is System.Linq.Enumerable.Range
The Arguments are:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is
This is an Parameter expression type
Type: System.Int32, Name: n, ByRef: False
This expression is a Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has arguments. They are:
This is an Parameter expression type
Type: System.Int32, Name: product, ByRef: False
This is an Parameter expression type
Type: System.Int32, Name: factor, ByRef: False
The expression body is:
This binary expression is a Multiply expression
The Left argument is:
This is an Parameter expression type
Type: System.Int32, Name: product, ByRef: False
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: factor, ByRef: False
扩展示例库

本部分中的示例演示访问和检查表达式树中的节点的核心技术。 我略过了很多可能需要的操作,以便专注于访问表达式树中的节点这一核心任务。

首先,访问者只处理整数常量。 常量值可以是任何其他数值类型,且 C# 语言支持这些类型之间的转换和提升。 此代码的更可靠版本可反映所有这些功能。

即使最后一个示例也只可识别可能的节点类型的一部分。 你仍可以向其添加许多将导致其失败的表达式。 完整的实现包含在名为 ExpressionVisitor 的 .NET 标准中,且可以处理所有可能的节点类型。

C#3.0新增功能10 表达式树 05 解释表达式的更多相关文章

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

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

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

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

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

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

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

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

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

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

  6. C#3.0新增功能10 表达式树 04 执行表达式

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

  7. C#3.0新增功能09 LINQ 基础05 使用 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. QtZint编译过程记录(要使用 QTMAKE_CFLAGS += /TP 参数)

    1,下载zint后,在zint-2.4.3\win32\vcx目录下找到zlib.props和libpng.props文件,分别改为zlib和libpng的源码目录.这2个开源库最好是找工程中使用的版 ...

  2. 使用PNG实现半透明的窗体(使用GDI+)

      Delphi中标准控件是不支持png图片的,据说从Window2000后增加gdiplus.dll库处理更多的gdi图像,其中包括png.   关键的几个api   GdipCreateBitma ...

  3. SYN3305A型 小型时统设备

       SYN3305A型  小型时统设备 产品概述 SYN3305A型小型时统设备是由西安同步电子科技有限公司精心设计.自行研发生产的一款高准确度的锁相石英频率标准.内装OCX0恒温晶体振荡器,利用G ...

  4. 304902阿里巴巴Java开发手册1.4.0

    转自官网 前言 <阿里巴巴Java开发手册>是阿里巴巴集团技术团队的集体智慧结晶和经验总结,经历了多次大规模一线实战的检验及不断完善,系统化地整理成册,回馈给广大开发者.现代软件行业的高速 ...

  5. comboBox控件动态绑定数据

    /// <summary>        /// load加载数据        /// </summary>        /// <param name=" ...

  6. Go语言学习——彻底弄懂return和defer的微妙关系

    疑问 前面在函数篇里介绍了Go语言的函数是支持多返回值的. 只要在函数体内,对返回值赋值,最后加上return就可以返回所有的返回值. 最近在写代码的时候经常遇到在return后,还要在defer里面 ...

  7. 18 HTML标签以及属性全

    基本结构标签: <HTML>,表示该文件为HTML文件 <HEAD>,包含文件的标题,使用的脚本,样式定义等 <TITLE>---</TITLE>,包含 ...

  8. 10分钟实现Typora(markdown)编辑器

    本章主要内容: 介绍我们将在接下来的几章中构建的应用程序 配置我们的CSS样式表,使其看起来更像一个本机应用程序 回顾在Electron中主进程和渲染器进程之间的关系 为我们的主进程和渲染器进程实现基 ...

  9. HDU 6053:TrickGCD(莫比乌斯反演)

    题目链接 题意 给出n个数,问在这n个数里面,有多少组bi(1<=bi<=ai)可以使得任意两个bi不互质. 思路 想法就是枚举2~min(ai),然后去对于每个ai都去除以这些质数,然后 ...

  10. scrapy基础知识之 scrapy 三种模拟登录策略:

    注意:模拟登陆时,必须保证settings.py里的 COOKIES_ENABLED (Cookies中间件) 处于开启状态 COOKIES_ENABLED = True或 # COOKIES_ENA ...