C# Expression学习笔记(一、表达式与表达式树的基本结构)

  • C# Expression学习笔记(一、表达式与表达式树的基本结构)已关闭评论
  • 33 次浏览
  • A+
所属分类:.NET技术
摘要

        昨天心血来潮,想着用了很久的HangFire这个任务调度组件,却从来没有研究过其源码,所以我就想着看一下Hangfire的源码,然后当我看到Hangfire源码中 AspNetShutdownDetector(Asp.Net服务停止检测器)这个类的源码的时候,看到其实现方式中,有两个私有的方法,看起名称是用于创建或者获取某个类中的静态字段和非静态字段的,但是看其实现则是通过表达式树进行反射查找。代码如下:


一个美丽的邂逅

        昨天心血来潮,想着用了很久的HangFire这个任务调度组件,却从来没有研究过其源码,所以我就想着看一下Hangfire的源码,然后当我看到Hangfire源码中 AspNetShutdownDetector(Asp.Net服务停止检测器)这个类的源码的时候,看到其实现方式中,有两个私有的方法,看起名称是用于创建或者获取某个类中的静态字段和非静态字段的,但是看其实现则是通过表达式树进行反射查找。代码如下:

 private static Func<T> CreateGetStaticFieldDelegate<T>(FieldInfo fieldInfo) {     var fieldExp = Expression.Field(null, fieldInfo);     return Expression.Lambda<Func<T>>(fieldExp).Compile(); }  private static Func<object, T> CreateGetFieldDelegate<T>(FieldInfo fieldInfo, Type type) {     var instExp = Expression.Parameter(typeof(object));     var convExp = Expression.Convert(instExp, type);     var fieldExp = Expression.Field(convExp, fieldInfo);     return Expression.Lambda<Func<object, T>>(fieldExp, instExp).Compile(); }  

        事情突然就变得有趣起来了,因为本人是个小菜鸡,日常来说只会通过Expression来进行数据库条件查询这种基础操作,还真没有考虑过更深层次的操作,但是,人菜瘾大,又菜又爱研究,所以一下就被这个操作给吸引住了,然后越看越感兴趣,之前居然没想到表达式树居然还能进行反射操作,所以二话不说就抛弃了Hangfire的源码(毕竟得先搞懂这种操作的实现原理才能明白人家写这个用意嘛)开始专心研究起表达式树来了。

初识Expression

        首先,我选择先来了解一下Expression的概念及相关的方法(说实话以前真没有认真研究过,实在是惭愧), 而了解这个玩意的最好地方就是微软官方给出的文档,文档原文:Expression是一个抽象类,他主要是表示表达式树节点的类派生的基类。而其派生的类如下:

//表示具有二进制运算符的表达式 System.Linq.Expressions.BinaryExpression  //表示包含一个表达式序列的块,表达式中可定义变量。 System.Linq.Expressions.BlockExpression //表示具有条件运算符的表达式。 System.Linq.Expressions.ConditionalExpression  //表示具有常数值的表达式 System.Linq.Expressions.ConstantExpression  //发出或清除调试信息的序列点。 这使调试器能够在调试时突出显示正确的源代码。 System.Linq.Expressions.DebugInfoExpression  //表示一个类型或空表达式的默认值。 System.Linq.Expressions.DefaultExpression //表示一个动态操作 System.Linq.Expressions.DynamicExpression //表示无条件跳转。 这包括返回语句,break 和 continue 语句以及其他跳转。 System.Linq.Expressions.GotoExpression //表示对一个属性或数组进行索引。 System.Linq.Expressions.IndexExpression //表示一个将委托或 Lambda 表达式应用到一个自变量表达式列表的表达式。 System.Linq.Expressions.InvocationExpression  //表示一个标签,可以将该标签放置在任何 Expression 上下文中。 //如果已跳转到该标签,则它将获取由对应的 GotoExpression 提供的值。  //否则,它接收 DefaultValue 中的值。  //如果 Type 等于 System.Void,则不应提供值。 System.Linq.Expressions.LabelExpression  //介绍 lambda 表达式。 它捕获一个类似于 .NET 方法主体的代码块。 System.Linq.Expressions.LambdaExpression //表示具有集合初始值设定项的构造函数调用。 System.Linq.Expressions.ListInitExpression //表示无限循环。 可通过“中断”退出该循环。 System.Linq.Expressions.LoopExpression //表示访问字段或属性。 System.Linq.Expressions.MemberExpression //表示调用构造函数并初始化新对象的一个或多个成员。 System.Linq.Expressions.MemberInitExpression //表示对静态方法或实例方法的调用。 System.Linq.Expressions.MethodCallExpression //表示创建一个新数组,并可能初始化该新数组的元素。 System.Linq.Expressions.NewArrayExpression //表示一个构造函数调用。 System.Linq.Expressions.NewExpression //表示一个命名的参数表达式。 System.Linq.Expressions.ParameterExpression //一个为变量提供运行时读/写权限的表达式。 System.Linq.Expressions.RuntimeVariablesExpression //表示一个控制表达式,该表达式通过将控制传递到 SwitchCase 来处理多重选择。 System.Linq.Expressions.SwitchExpression //表示一个 try/catch/finally/fault 块。 System.Linq.Expressions.TryExpression //表示表达式和类型之间的操作。 System.Linq.Expressions.TypeBinaryExpression //表示具有一元运算符的表达式。 System.Linq.Expressions.UnaryExpression  

        由上面我们可以看出,Expression作为表达式树的一个基类,其派生了许多不同的子类,根据这些子类,我们可以实现不同的逻辑(此前真是没考虑过这方面,我以为只能去当作数据库查询语句呢,真特么惭愧),那么问题随之而来,我们应该怎么去应用这些子类,或者在什么时候可以运用他们呢?不急,今天我们首先简单了解一下Expression的相关概念及结构,先把基类研究明白了,其他子类日后可以慢慢研究。

解析

        在此之前,我们先理解一下什么是表达式,表达式是由多个运算符和操作数组成,其中运算符表示要进行的操作,而操作数可以是一个变量、常量或者固定值。举例如下:

  a>b;   a=1;   a=100;   var a=1+2;   a+b+c; 

以上这些都属于表达式,从上面的代码我可以看出,表达式的结构最简单可以分为左操作数,运算符,右操作数。三个基本的元素组成。

        那么什么又是表达式树?官方给出的说法是:表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,这句话其实我刚开始理解起来呢,有些不太理解,但是在阅读了几篇关于表达式树的文章以后,大致有了一些理解。我的理解:表达式树就是一个可以拆分为多个子表达式的表达式所展开后的树形结构,具体如下:

  a=1+2; 

通过前面关于表达式介绍我们可以知道,这是一个包含加法表达式的赋值表达式,我们可以把它看作是一个表达式树,那么作为一个树形结构,我们首先把这个表达式本身看作最顶层的节点(树梢)。按照表达式的基本结构,我们首先看到左操作数(a),运算符(=),右操作符(1+2),确定好顶层节点以后,我们开始往下去展开列出表达式的子节点,首先,该左操作数只有一个变量,那么该表达式树的第一个子节点就是左操作数变量'a',然后第二个节点就是运算符 '=',而第三个节点则是右操作数(1+2),现在我们进一步将(1+2)看做成一个加法表达式,然后我们往下继续展开寻找子节点,那么该表达式的第一个子节点就是固定值1,第二个子节点则是运算符+,第三个子节点则是固定值2。至此,所有子节点均为个操作数或运算符,无法再继续往下展开,该表达式树结构就结束了。为了更直观的展示,我画了个结构图,其结构图如下:

C# Expression学习笔记(一、表达式与表达式树的基本结构)

        那么,这样一个结构,我们在代码中如何使用表达式树来进行标识呢?让我们来看下图:

C# Expression学习笔记(一、表达式与表达式树的基本结构)

由上图可以看出,我们先使用Constant方法定义出一个ConstantException类型的表达式作为左操作数,同理我们再声明出一个右操作数,之后,通过Expression提供的Add方法或者MakeBinary方法指定从需要操作的运算以及左右操作数,从而就会生成一个BinaryExpression类型的表达式,然后我们再通过Parameter方法生命出一个变量表达式,之后通过Assign(赋值表达式)将其组合起来,就又生成了一个全新的表达式,通过输出我们可以看到,其结构与我们上面的表达式结构一毛一样。所以这就是C#表达式树相关的整个结构与基本操作。