概述

在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的趋势。LINQ本身也提供了很好的扩展性,使得我们可以轻松的编写属于自己的LINQ Provider。

本文为打造自己的LINQ Provider系列文章第一篇,主要介绍表达式目录树(Expression Tree)的相关知识。

认识表达式目录树

究竟什么是表达式目录树(Expression Tree),它是一种抽象语法树或者说它是一种数据结构,通过解析表达式目录树,可以实现我们一些特定的功能(后面会说到),我们首先来看看如何构造出一个表达式目录树,最简单的方法莫过于使用Lambda表达式,看下面的代码:

Expression<Func<int, int, int>> expression = (a, b) => a * b + 2;

在我们将Lambda表达式指定给Expression<TDelegate>类型的变量(参数)时,编译器将会发出生成表达式目录树的指令,如上面这段代码中的Lambda表达式(a, b) => a * b + 2将创建一个表达式目录树,它表示的是一种数据结构,即我们把一行代码用数据结构的形式表示了出来,具体来说最终构造出来的表达式目录树形状如下图所示:

这里每一个节点都表示一个表达式,可能是一个二元运算,也可能是一个常量或者参数等,如上图中的ParameterExpression就是一个参数表达式,ConstantExpression是一个常量表达式,BinaryExpression是一个二元表达式。我们也可以在Visual Studio中使用Expression Tree Visualizer来查看该表达式目录树:

查看结果如下图所示:

这里说一句,Expression Tree Visualizer可以从MSDN Code Gallery上的LINQ Sample中得到。现在我们知道了表达式目录树的组成,来看看.NET Framework到底提供了哪些表达式?如下图所示:

它们都继承于抽象的基类Expression,而泛型的Expression<TDelegate>则继承于LambdaExpression。在Expression类中提供了大量的工厂方法,这些方法负责创建以上各种表达式对象,如调用Add()方法将创建一个表示不进行溢出检查的算术加法运算的BinaryExpression对象,调用Lambda方法将创建一个表示lambda 表达式的LambdaExpression对象,具体提供的方法大家可以查阅MSDN。上面构造表达式目录树时我们使用了Lambda表达式,现在我们看一下如何通过这些表达式对象手工构造出一个表达式目录树,如下代码所示:

static void Main(string[] args)
{ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);ConstantExpression conRight = Expression.Constant(2, typeof(int));BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);LambdaExpression lambda = Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);Console.WriteLine(lambda.ToString());Console.Read();
}

这里构造的表达式目录树,仍然如下图所示:

运行这段代码,看看输出了什么:

可以看到,通过手工构造的方式,我们确实构造出了同前面一样的Lambda表达式。对于一个表达式目录树来说,它有几个比较重要的属性:

Body:指表达式的主体部分;

Parameters:指表达式的参数;

NodeType:指表达式的节点类型,如在上面的例子中,它的节点类型是Lambda;

Type:指表达式的静态类型,在上面的例子中,Type为Fun<int,int,int>。

在Expression Tree Visualizer中,我们可以看到表达式目录树的相关属性,如下图所示:

表达式目录树与委托

大家可能经常看到如下这样的语言,其中第一句是直接用Lambda表达式来初始化了Func委托,而第二句则使用Lambda表达式来构造了一个表达式目录树,它们之间的区别是什么呢?

static void Main(string[] args)
{Func<int, int, int> lambda = (a, b) => a + b * 2;Expression<Func<int, int, int>> expression = (a, b) => a + b * 2;
} 

其实看一下IL就很明显,其中第一句直接将Lambda表达式直接编译成了IL,如下代码所示:

.method private hidebysig static void  Main(string[] args) cil managed
{.entrypoint.maxstack  3.locals init ([0] class [System.Core]System.Func`3<int32,int32,int32> lambda)IL_0000:  nopIL_0001:  ldsfld     class [System.Core]System.Func`3<int32,int32,int32> TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'IL_0006:  brtrue.s   IL_001bIL_0008:  ldnullIL_0009:  ldftn      int32 TerryLee.LinqToLiveSearch.Program::'<Main>b__0'(int32,int32)IL_000f:  newobj     instance void class [System.Core]System.Func`3<int32,int32,int32>::.ctor(object,native int)IL_0014:  stsfld     class [System.Core]System.Func`3<int32,int32,int32> TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'IL_0019:  br.s       IL_001bIL_001b:  ldsfld     class [System.Core]System.Func`3<int32,int32,int32> TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'IL_0020:  stloc.0IL_0021:  ret
}

而第二句,由于告诉编译器是一个表达式目录树,所以编译器会分析该Lambda表达式,并生成表示该Lambda表达式的表达式目录树,即它与我们手工创建表达式目录树所生成的IL是一致的,如下代码所示,此处为了节省空间省略掉了部分代码:

.method private hidebysig static void  Main(string[] args) cil managed
{.entrypoint.maxstack  4.locals init ([0] class [System.Core]System.Linq.Expressions.Expression`1<class [System.Core]System.Func`3<int32,int32,int32>> expression,[1] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000,[2] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0001,[3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0002)IL_0000:  nopIL_0001:  ldtoken    [mscorlib]System.Int32IL_0006:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(...)IL_000b:  ldstr      "a"IL_0010:  call       class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type,IL_0038:  call    class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle()IL_003d:  call    class [System.Core]System.Linq.Expressions.ConstantExpression [System.Core]System.Linq.Expressions.Expression::Constant(object,class [mscorlib]System.Type)IL_0042:  call    class [System.Core]System.Linq.Expressions.BinaryExpression [System.Core]System.Linq.Expressions.Expression::Multiply(class [System.Core]System.Linq.Expressions.Expression,class [System.Core]System.Linq.Expressions.Expression)IL_0047:  call    class [System.Core]System.Linq.Expressions.BinaryExpression[System.Core]System.Linq.Expressions.Expression::Add(class [System.Core]System.Linq.Expressions.Expression,class [System.Core]System.Linq.Expressions.Expression)IL_004c:  ldc.i4.2IL_004d:  newarr     [System.Core]System.Linq.Expressions.ParameterExpression
}

现在相信大家都看明白了,这里讲解它们的区别主要是为了加深大家对于表达式目录树的区别。

执行表达式目录树

前面已经可以构造出一个表达式目录树了,现在看看如何去执行表达式目录树。我们需要调用Compile方法来创建一个可执行委托,并且调用该委托,如下面的代码:

static void Main(string[] args)
{ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);ConstantExpression conRight = Expression.Constant(2, typeof(int));BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);Expression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);Func<int, int, int> myLambda = lambda.Compile();int result = myLambda(2, 3);Console.WriteLine("result:" + result.ToString());Console.Read();
}

运行后输出的结果:

这里我们只要简单的调用Compile方法就可以了,事实上在.NET Framework中是调用了一个名为ExpressionCompiler的内部类来做表达式目录树的执行(注意此处的Compiler不等同于编译器的编译)。另外,只能执行表示Lambda表达式的表达式目录树,即LambdaExpression或者Expression<TDelegate>类型。如果表达式目录树不是表示Lambda表达式,需要调用Lambda方法创建一个新的表达式。如下面的代码:

static void Main(string[] args)
{BinaryExpression body = Expression.Add(Expression.Constant(2),Expression.Constant(3));Expression<Func<int>> expression = Expression.Lambda<Func<int>>(body, null);Func<int> lambda = expression.Compile();Console.WriteLine(lambda());
}

访问与修改表达式目录树

在本文一开始我就说过, 通过解析表达式目录树,我们可以实现一些特定功能,既然要解析表达式目录树,对于表达式目录树的访问自然是不可避免的。在.NET Framework中,提供了一个抽象的表达式目录树访问类ExpressionVisitor,但它是一个internal的,我们不能直接访问。幸运的是,在MSDN中微软给出了ExpressionVisitor类的实现,我们可以直接拿来使用。该类是一个抽象类,微软旨在让我们在集成ExpressionVisitor的基础上,实现自己的表达式目录树访问类。现在我们来看简单的表达式目录树:

static void Main(string[] args)
{Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;Console.WriteLine(lambda.ToString());
} 

输出后为:

现在我们想要修改表达式目录树,让它表示的Lambda表达式为(a,b)=>(a - (b * 2)),这时就需要编写自己的表达式目录树访问器,如下代码所示:

public class OperationsVisitor : ExpressionVisitor
{public Expression Modify(Expression expression){return Visit(expression);}protected override Expression VisitBinary(BinaryExpression b){if (b.NodeType == ExpressionType.Add){Expression left = this.Visit(b.Left);Expression right = this.Visit(b.Right);return Expression.Subtract(left,right);}return base.VisitBinary(b);}
}

使用表达式目录树访问器来修改表达式目录树,如下代码所示:

static void Main(string[] args)
{Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;var operationsVisitor = new OperationsVisitor();Expression modifyExpression = operationsVisitor.Modify(lambda);Console.WriteLine(modifyExpression.ToString());
}

运行后可以看到输出:

似乎我们是修改表达式目录树,其实也不全对,我们只是修改表达式目录树的一个副本而已,因为表达式目录树是不可变的,我们不能直接修改表达式目录树,看看上面的OperationsVisitor类的实现大家就知道了,在修改过程中复制了表达式目录树的节点。

为什么需要表达式目录树

通过前面的介绍,相信大家对于表达式目录树已经有些了解了,还有一个很重要的问题,就是为什么需要表达式目录树?在本文开始时,就说过通过解析表达式目录树,可以实现我们一些特定的功能,就拿LINQ to SQL为例,看下面这幅图:

当我们在C#语言中编写一个查询表达式时,它将返回一个IQueryable类型的值,在该类型中包含了两个很重要的属性Expression和Provider,如下面的代码:

我们编写的查询表达式,将封装为一种抽象的数据结构,这个数据结构就是表达式目录树,当我们在使用上面返回的值时,编译器将会以该值所期望的方式进行翻译,这种方式就是由Expression和Provider来决定。可以看到,这样将会非常的灵活且具有良好的可扩展性,有了表达式目录树,可以自由的编写自己的Provider,去查询我们希望的数据源。经常说LINQ为访问各种不同的数据源提供了一种统一的编程方式,其奥秘就在这里。然而需要注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因为它并不翻译为表达式目录树,后面会说到这一点。

原文链接:打造自己的LINQ Provider(上):Expression Tree揭秘

作者:李会军.

[转]打造自己的LINQ Provider(上):Expression Tree揭秘相关推荐

  1. QueryBuilder : 打造优雅的Linq To SQL动态查询

    首先我们来看看日常比较典型的一种查询Form 这个场景很简单:就是根据客户名.订单日期.负责人来作筛选条件,然后找出符合要求的订单. 在那遥远的时代,可能避免不了要写这样的简单接口: public i ...

  2. ABP +VUE Elment 通用高级查询(右键菜单)设计+LINQ通用类Expression<Func<TFields, bool>>方法

    ABP +VUE Elment 通用高级查询(右键菜单)设计+LINQ通用类Expression 1. 目前需要用VUE实现源cs系统报表的右键菜单所有和自定义查询功能. 1.1 CS端的右键菜单效果 ...

  3. 夯实安全“三大体系”建设,腾讯云打造安全可靠的云上高速公路

    产业互联网时代,5G.AI.云计算等新一代信息技术与应用不断深化,加速了各行业数字化和产业升级的进程.在企业上云.资产数字化的背景下,安全不只是企业经营的底线,更是制约企业发展的天花板.越来越多的企业 ...

  4. Expression Tree 上手指南 (二)

    上回我们说到Expression Tree是一种表示编程语言中"表达式"概念的树状数据结构,并且学习了从Lambda表达式自动生成表达式树的C#语法.那么它到底有什么用呢?其实上一 ...

  5. 三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate

    在<上篇>中,我比较了三种属性操作的性能:直接操作,单纯通过PropertyInfo反射和IL Emit.本篇继续讨论这个话题,我们再引入另外两种额外的属性操作方式:Expression ...

  6. 一起谈.NET技术,关于Expression Tree和IL Emit的所谓的quot;性能差别quot;

    昨天写了<三种属性操作性能比较>,有个网友写信问我一个问题:从性能上看,Expression Tree和IL Emit孰优孰劣?虽然我在回信中作了简单的回答,但不知道这个网友是否懂我的意思 ...

  7. NET(C#):使用Expression Tree构建带有参数、本地变量和返回值的Lambda

    .NET(C#):使用Expression Tree构建带有参数.本地变量和返回值的Lambda 对于参数,需要使用Expression.Parameter创建ParameterExpression对 ...

  8. 反射,Expression Tree,IL Emit 属性操作对比

    .net的反射(Reflection) 是.Net中获取运行时类型信息的一种方法,通过反射编码的方式可以获得 程序集,模块,类型,元数据等信息. 反射的优点在于微软提供的API调用简单,使用方便: 表 ...

  9. 快速上手Expression Tree(一):做一做装配脑袋的Expression Tree 习题

    装配脑袋的习题在这里:Expression Tree上手指南 (一) 不了解Expression Tree的同学可以去看下,很好,很强大. 1: -a 2: a + b * 2 我把这些问题都弄成了方 ...

最新文章

  1. 用什么擦地最干净脑筋急转弯_22个数学脑筋急转弯答案,你能全部做出来吗?...
  2. 二叉树-路径总和(递归)
  3. 2022年值得关注的8个人工智能趋势
  4. 时间复杂度O(n),空间复杂度O(1)的排序
  5. python的上下文管理
  6. 基于Android Studio搭建Android应用开发环境
  7. eclipse修改java类时不自动重启
  8. iphone iPhone开发中如何将制作图片放大缩小代码实现案例
  9. 阶段1 语言基础+高级_1-3-Java语言高级_08-JDK8新特性_第4节 方法引用_2_方法引用_通过对象名引用成员方法...
  10. Huffman实现对26个英文字母的编码
  11. .Net程序员的职业规划
  12. GitHub 9K Star!Apollo作者手把手教你微服务配置中心之道
  13. abb机器人离线编程软件叫做_工业机器人离线编程(ABB)1-2 常用离线编程软件介绍.pptx...
  14. 从编译器源码中提取ARMv8的指令编码
  15. 中国医科大学2021年12月《中医护理学基础》作业考核试题
  16. 程序员为程序员推荐:我觉得这本书不错,分享给你
  17. elementui穿梭框数据不更新
  18. 纯干货!live2d动画制作简述以及踩坑
  19. android 图片上动态添加文字,摘抄 android图片中添加文字水印
  20. 【pytorch】将模型部署至生产环境:借助TorchScript跟踪法及注释法生成可供C++调用的模块

热门文章

  1. 2020计算机二级题库第14PPT,计算机二级考试MSOffice考精彩试题库ppt操作题附问题详解.doc...
  2. go设计模式思维导图
  3. redis源码剖析(四)跳表
  4. net-tools和ifconfig
  5. 二叉树题目----6 二叉树的最近公共祖先 AND 二叉树搜索树转换成排序双向链表
  6. IA-32 Architecture: the function of segment regitster(CS DS SS ES)
  7. pthread_join函数
  8. oppoJava面试题,java声明全局变量的关键字
  9. python 编码规范
  10. Centos 6.5安装MySQL-python