草庐IT

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

人之初 2023-03-28 原文

一个美丽的邂逅

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


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

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

有关C# Expression学习笔记(一、表达式与表达式树的基本结构)的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  3. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  4. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  5. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  6. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  7. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  8. ruby - 正则表达式将非英文字母匹配为非单词字符 - 2

    @raw_array[i]=~/[\W]/非常简单的正则表达式。当我用一些非拉丁字母(具体来说是俄语)尝试时,条件是错误的。我能用它做什么? 最佳答案 @raw_array[i]=~/[\p{L}]/使用西里尔字符进行测试。引用:http://www.regular-expressions.info/unicode.html#prop 关于ruby-正则表达式将非英文字母匹配为非单词字符,我们在StackOverflow上找到一个类似的问题: https://

  9. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  10. ruby-on-rails - 一般建议和推荐的文件夹结构 - Sinatra - 2

    您将如何构建一个简单的Sinatra应用程序?我正在制作,我希望该应用具有以下功能:“应用程序”更像是一个包含所有信息的管理仪表板。然后另一个应用程序将通过REST访问信息。我还没有创建仪表板,只是从数据库中获取东西session和身份验证(尚未实现)您可以上传图片,其他应用可以显示这些图片我已经使用RSpec创建了一个测试文件通过Prawn生成报告目前的设置是这样的:app.rbtest_app.rb因为我实际上只有应用程序和测试文件。到目前为止,我已经将Datamapper用于ORM,将SQLite用于数据库。这是我的第一个Ruby/Sinatra项目,所以欢迎任何和所有建议-我应

随机推荐