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

一个美丽的邂逅

        昨天心血来潮,想着用了很久的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。至此,所有子节点均为个操作数或运算符,无法再继续往下展开,该表达式树结构就结束了。为了更直观的展示,我画了个结构图,其结构图如下:

1666099934017.jpg


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

1666104724435.jpg

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容