C# 表达式树Expression Trees的知识梳理

目录

  • 简介
  • Lambda 表达式创建表达式树
  • API 创建表达式树
  • 解析表达式树
  • 表达式树的永久性
  • 编译表达式树
  • 执行表达式树
  • 修改表达式树
  • 调试

简介

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。

你可以对表达式树中的代码进行编辑和运算。这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。

表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性。

一、Lambda 表达式创建表达式树

若 lambda 表达式被分配给 Expression<TDelegate> 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。

C# 编译器只能从表达式 lambda (或单行 lambda)生成表达式树。

下列代码示例使用关键字 Expression创建表示 lambda 表达式:

 Expression<Action<int>> actionExpression = n => Console.WriteLine(n);
 Expression<Func<int, bool>> funcExpression1 = (n) => n < 0;
 Expression<Func<int, int, bool>> funcExpression2 = (n, m) => n - m == 0;

二、API 创建表达式树

通过 API 创建表达式树需要使用Expression

下列代码示例展示如何通过 API 创建表示 lambda 表达式:num => num == 0

//通过 Expression 类创建表达式树
 // lambda:num => num == 0
 ParameterExpression pExpression = Expression.Parameter(typeof(int)); //参数:num
 ConstantExpression cExpression = Expression.Constant(0); //常量:0
 BinaryExpression bExpression = Expression.MakeBinary(ExpressionType.Equal, pExpression, cExpression); //表达式:num == 0
 Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(bExpression, pExpression); //lambda 表达式:num => num == 0

代码使用Expression类的静态方法进行创建。

三、解析表达式树

下列代码示例展示如何分解表示 lambda 表达式 num => num == 0 的表达式树。

Expression<Func<int, bool>> funcExpression = num => num == 0;
 //开始解析
 ParameterExpression pExpression = funcExpression.Parameters[0]; //lambda 表达式参数
 BinaryExpression body = (BinaryExpression)funcExpression.Body; //lambda 表达式主体:num == 0
 Console.WriteLine($"解析:{pExpression.Name} => {body.Left} {body.NodeType} {body.Right}");

四、表达式树永久性

表达式树应具有永久性(类似字符串)。这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。  你可以使用表达式树访问者遍历现有表达式树。第七节介绍了如何修改表达式树。

五、编译表达式树

Expression<TDelegate> 类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。

//创建表达式树
 Expression<Func<string, int>> funcExpression = msg => msg.Length;
 //表达式树编译成委托
 var lambda = funcExpression.Compile();
 //调用委托
 Console.WriteLine(lambda("Hello, World!"));
 //语法简化
 Console.WriteLine(funcExpression.Compile()("Hello, World!"));

六、执行表达式树

执行表达式树可能会返回一个值,也可能仅执行一个操作(例如调用方法)。

只能执行表示 lambda 表达式的表达式树。表示 lambda 表达式的表达式树属于 LambdaExpressionExpression<TDelegate> 类型。若要执行这些表达式树,需要调用 Compile方法来创建一个可执行委托,然后调用该委托。

const int n = 1;
 const int m = 2;
 //待执行的表达式树
 BinaryExpression bExpression = Expression.Add(Expression.Constant(n), Expression.Constant(m));
 //创建 lambda 表达式
 Expression<Func<int>> funcExpression = Expression.Lambda<Func<int>>(bExpression);
 //编译 lambda 表达式
 Func<int> func = funcExpression.Compile();
 //执行 lambda 表达式
 Console.WriteLine($"{n} + {m} = {func()}");

七、修改表达式树

该类继承 ExpressionVisitor 类,通过 Visit 方法间接调用 VisitBinary 方法将 != 替换成 ==。基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。

internal class Program
 {
 private static void Main(string[] args)
 {
 Expression<Func<int, bool>> funcExpression = num => num == 0;
 Console.WriteLine($"Source: {funcExpression}");
 var visitor = new NotEqualExpressionVisitor();
 var expression = visitor.Visit(funcExpression);
 Console.WriteLine($"Modify: {expression}");
 Console.Read();
 }
 /// <summary>
 /// 不等表达式树访问器
 /// </summary>
 public class NotEqualExpressionVisitor : ExpressionVisitor
 {
 public Expression Visit(BinaryExpression node)
 {
 return VisitBinary(node);
 }
 protected override Expression VisitBinary(BinaryExpression node)
 {
 return node.NodeType == ExpressionType.Equal
  ? Expression.MakeBinary(ExpressionType.NotEqual, node.Left, node.Right) //重新弄个表达式:用 != 代替 ==
  : base.VisitBinary(node);
 }
 }
 }

八、调试

8.1 参数表达式

 ParameterExpression pExpression1 = Expression.Parameter(typeof(string));
 ParameterExpression pExpression2 = Expression.Parameter(typeof(string), "msg");

图8-1

图8-2

从 DebugView 可知,如果参数没有名称,则会为其分配一个自动生成的名称。

 const int num1 = 250;
 const float num2 = 250;
 ConstantExpression cExpression1 = Expression.Constant(num1);
 ConstantExpression cExpression2 = Expression.Constant(num2);

图8-3

图8-4

从 DebugView 可知,float 比 int 多了个后缀 F。

 Expression lambda1 = Expression.Lambda<Func<int>>(Expression.Constant(250));
 Expression lambda2 = Expression.Lambda<Func<int>>(Expression.Constant(250), "CustomName", null);

图8-5

图8-6

观察 DebugView ,如果 lambda 表达式没有名称,则会为其分配一个自动生成的名称。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我们!

(0)

相关推荐

  • c#装箱和拆箱知识整理

    1.装箱和拆箱是一个抽象的概念 2.装箱是将值类型转换为引用类型 : 拆箱是将引用类型转换为值类型 利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链接起来 例如: 复制代码 代码如下: int val = 100; object obj = val; Console.WriteLine ("对象的值 = {0}", obj); 这是一个装箱的过程,是将值类型转换为引用类型的过程 复制代码 代码如下: int val = 100; obj

  • C#进阶系列 WebApi身份认证解决方案推荐:Basic基础认证

    前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想想都恐怖.经过一番折腾,总算是加上了接口的身份认证,在此记录下,也给需要做身份认证的园友们提供参考. 一.为什么需要身份认证 在前言里面,我们说了,如果没有启用身份认证,那么任何匿名用户只要知道了我们服务的url,就能随意访问我们的服务接口,从而访问或修改数据库. 1.我们不加身份认证,匿名用户可以

  • C#知识整理

    这里简单介绍了一些常用的属性,以及一些术语的解释和举例说明,不太全面,希望读者多多补充. 1.重载:函数名相同,参数的个数或参数类型不同; public void MyDog(string s); public void MyDog(int i); public void MyDog(string s,int i); 2.继承:一个类继承另一个类中的成员,被继承的叫做基类,继承类叫做派生类; class A { 成员; } class B:A //继承的写法 派生类:基类 { 成员; } 3.多

  • C# 6.0 的知识梳理

    序 目前最新的版本是 C# 7.0,VS 的最新版本为 Visual Studio 2017 RC,两者都尚未进入正式阶段.C# 6.0 虽说出了一段时间,但是似乎有许多园友对这一块知识并不了解,如拼接字符串的 $ 符号,在此,小人献上拙作一篇<C# 6.0 的知识梳理>,祝大家在新的一年里:年年有今日,岁岁有今朝,月月涨工资,周周中彩票,天天好心情,日日好运道,白天遇财神,夜晚数钞票. 好了,废话不多说,我们先来回顾一下 C# 的版本史.后续我会对带 0 的版本号进行的简写:C# 6.0 -

  • C#网络编程基础之进程和线程详解

    在C#的网络编程中,进程和线程是必备的基础知识,同时也是一个重点,所以我们要好好的掌握一下. 一:概念 首先我们要知道什么是"进程",什么是"线程",好,查一下baike. 进程:是一个具有一定独立功能的程序关于某个数据集合的一次活动.它是操作系统动态执行的基本单元, 在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元. 线程:是"进程"中某个单一顺序的控制流. 关于这两个概念,大家稍微有个印象就行了,防止以后被面试官问到. 二:进程

  • C#学习笔记整理_变量等基础语法(必看篇)

    C#学习笔记1: 变量的作用域冲突时,调用实例变量:this.a,调用类变量:类名.a 常量总是静态的,必须初始化,一般用全大写格式,声明关键字为const,如const int NUNBE = 10; C#的基本预定义类型内置于.NET Framework结构中(System),object是基类: 整型:System.SByte.System.Int16.System.Int32.System.Int64 有符号的8位.16位.32位.64位分别表示为sbyte.short.int.long

  • c#多线程编程基础

    无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作.要使应用程序能够快速响应用户操作,同时在用户事件之间或者甚至在用户事件期间利用处理器,最强大的方式之一是使用多线程技术. 多线程:线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.如果某个线程进行一次长延迟操作, 处理器就切换到另一个线程执行.这样,多个线程的并行(并发)执行隐藏了长延迟,提高了处理器资源利用率

  • c#基础之数组与接口使用示例(遍历数组 二维数组)

    一.初始化数组: 复制代码 代码如下: string[] s1 = {"aaa","bbb","ccc"}   //直接赋值string[] s2 = new string[5] {"aaa","bbb","ccc"}; //赋值加指定长度string[] s3 =  new string[]{"aaa","bbb","ccc"

  • C#基于TCP协议的服务器端和客户端通信编程的基础教程

    运行在TCP之上常见的网络应用协议有比如HTTP.FTP.SMTP.POP3.IMAP. TCP是TCP/IP体系中最重要的传输协议,它提供全双工和可靠交付的服务,是大多数应用协议工作的基础. TCP是一种面向连接(连接导向)的,可靠的,基于字节流的传输层通信协议. TCP的工作过程 建立连接 传输数据 连接的终止 TCP的主要特点 1.TCP是面向连接的协议 2.是端到端的通信.每个TCP连接只能有两个端点,而且只能一对一通信,不能点对多的 的直接通信 3.高可靠性 4.全双工方式传输 5.数

  • C#基础之匿名方法实例教程

    本文以实例形式讲解了C#的匿名方法的用法,分享给大家供大家参考之用.具体如下: 匿名方法是C# 2.0的语言新特性.首先看个最简单的例子: class Program { static void Main(string[] args) { List<string> names = new List<string>(); names.Add("Sunny Chen"); names.Add("Kitty Wang"); names.Add(&q

随机推荐