概述

在.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表达式,看下面的代码:

  1. 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表达式,现在我们看一下如何通过这些表达式对象手工构造出一个表达式目录树,如下代码所示:

  1. static void Main(string[] args)
  2. {
  3. ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
  4. ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
  5.  
  6. BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
  7. ConstantExpression conRight = Expression.Constant(2, typeof(int));
  8.  
  9. BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
  10.  
  11. LambdaExpression lambda =
  12. Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);
  13.  
  14. Console.WriteLine(lambda.ToString());
  15.  
  16. Console.Read();
  17. }

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

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

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

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

Parameters:指表达式的参数;

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

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

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

表达式目录树与委托

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

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

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

  1. .method private hidebysig static void Main(string[] args) cil managed
  2. {
  3. .entrypoint
  4. .maxstack 3
  5. .locals init ([0] class [System.Core]System.Func`3<int32,int32,int32> lambda)
  6. IL_0000: nop
  7. IL_0001: ldsfld class [System.Core]System.Func`3<int32,int32,int32>
  8. TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  9. IL_0006: brtrue.s IL_001b
  10. IL_0008: ldnull
  11. IL_0009: ldftn int32 TerryLee.LinqToLiveSearch.Program::'<Main>b__0'(int32,
  12. int32)
  13. IL_000f: newobj instance void class [System.Core]System.Func`3<int32,int32,int32>::.ctor(object,
  14. native int)
  15. IL_0014: stsfld class [System.Core]System.Func`3<int32,int32,int32>
  16. TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  17. IL_0019: br.s IL_001b
  18. IL_001b: ldsfld class [System.Core]System.Func`3<int32,int32,int32>
  19. TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  20. IL_0020: stloc.0
  21. IL_0021: ret
  22. }

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

  1. .method private hidebysig static void Main(string[] args) cil managed
  2. {
  3. .entrypoint
  4. .maxstack 4
  5. .locals init ([0] class [System.Core]System.Linq.Expressions.Expression`1<
  6. class [System.Core]System.Func`3<int32,int32,int32>> expression,
  7. [1] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000,
  8. [2] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0001,
  9. [3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0002)
  10. IL_0000: nop
  11. IL_0001: ldtoken [mscorlib]System.Int32
  12. IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(...)
  13. IL_000b: ldstr "a"
  14. IL_0010: call class [System.Core]System.Linq.Expressions.ParameterExpression
  15. [System.Core]System.Linq.Expressions.Expression::Parameter(
  16. class [mscorlib]System.Type,
  17.  
  18. IL_0038: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle()
  19. IL_003d: call class [System.Core]System.Linq.Expressions.ConstantExpression
  20. [System.Core]System.Linq.Expressions.Expression::Constant(object,
  21. class [mscorlib]System.Type)
  22. IL_0042: call class [System.Core]System.Linq.Expressions.BinaryExpression
  23. [System.Core]System.Linq.Expressions.Expression::Multiply(class [System.Core]System.Linq.Expressions.Expression,
  24. class [System.Core]System.Linq.Expressions.Expression)
  25. IL_0047: call class [System.Core]System.Linq.Expressions.BinaryExpression
  26. [System.Core]System.Linq.Expressions.Expression::Add(class [System.Core]System.Linq.Expressions.Expression,
  27. class [System.Core]System.Linq.Expressions.Expression)
  28. IL_004c: ldc.i4.2
  29. IL_004d: newarr [System.Core]System.Linq.Expressions.ParameterExpression
  30. }

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

执行表达式目录树

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

  1. static void Main(string[] args)
  2. {
  3. ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
  4. ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
  5.  
  6. BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
  7. ConstantExpression conRight = Expression.Constant(2, typeof(int));
  8.  
  9. BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
  10.  
  11. Expression<Func<int, int, int>> lambda =
  12. Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);
  13.  
  14. Func<int, int, int> myLambda = lambda.Compile();
  15.  
  16. int result = myLambda(2, 3);
  17. Console.WriteLine("result:" + result.ToString());
  18.  
  19. Console.Read();
  20. }

运行后输出的结果:

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

  1. static void Main(string[] args)
  2. {
  3. BinaryExpression body = Expression.Add(
  4. Expression.Constant(2),
  5. Expression.Constant(3));
  6.  
  7. Expression<Func<int>> expression =
  8. Expression.Lambda<Func<int>>(body, null);
  9.  
  10. Func<int> lambda = expression.Compile();
  11.  
  12. Console.WriteLine(lambda());
  13. }

访问与修改表达式目录树

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

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

输出后为:

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

  1. public class OperationsVisitor : ExpressionVisitor
  2. {
  3. public Expression Modify(Expression expression)
  4. {
  5. return Visit(expression);
  6. }
  7.  
  8. protected override Expression VisitBinary(BinaryExpression b)
  9. {
  10. if (b.NodeType == ExpressionType.Add)
  11. {
  12. Expression left = this.Visit(b.Left);
  13. Expression right = this.Visit(b.Right);
  14. return Expression.Subtract(left,right);
  15. }
  16.  
  17. return base.VisitBinary(b);
  18. }
  19. }

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

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

运行后可以看到输出:

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

为什么需要表达式目录树

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

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

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

总结

本为详细介绍了表达式目录树的相关知识,为我们编写自己的LINQ Provider打下一个基础,希望对于大家有所帮助。查看目前网上的各种lINQ Provider,请访问万般皆LINQ

转载Expression Tree揭秘的更多相关文章

  1. [转]打造自己的LINQ Provider(上):Expression Tree揭秘

    概述 在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHiber ...

  2. 打造自己的LINQ Provider(上):Expression Tree揭秘

    概述 在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHiber ...

  3. Expression Tree 扩展MVC中的 HtmlHelper 和 UrlHelper

    表达式树是LINQ To everything 的基础,同时各种类库的Fluent API也 大量使用了Expression Tree.还记得我在不懂expression tree时,各种眼花缭乱的A ...

  4. Expression Tree上手指南 (一)【转】

    大家可能都知道Expression Tree是.NET 3.5引入的新增功能.不少朋友们已经听说过这一特性,但还没来得及了解.看看博客园里的老赵等诸多牛人,将Expression Tree玩得眼花缭乱 ...

  5. Expression Tree Basics 表达式树原理

    variable point to code variable expression tree data structure lamda expression anonymous function 原 ...

  6. 使用Expression Tree构建动态LINQ查询

    这篇文章介绍一个有意思的话题,也是经常被人问到的:如何构建动态LINQ查询?所谓动态,主要的意思在于查询的条件可以随机组合,动态添加,而不是固定的写法.这个在很多系统开发过程中是非常有用的. 我这里给 ...

  7. Reflection和Expression Tree解析泛型集合快速定制特殊格式的Json

    很多项目都会用到Json,而且大部分的Json都是格式固定,功能强大,转换简单等,标准的key,value集合字符串:直接JsonConvert.SerializeObject(List<T&g ...

  8. .NET Expression Tree

    Expression Tree 第一个简单的例子. [TestMethod] public void GodTest() { Expression<Func<int, int, int&g ...

  9. Evaluation of Expression Tree

    Evaluation of Expression Tree Given a simple expression tree, consisting of basic binary operators i ...

随机推荐

  1. MyBatis-Spring 执行SQL语句的流程

    1. 从SqlSessionDaoSupport开始 通常我们使用MyBatis会让自己的DAO继承SqlSessionDaoSupport,那么SqlSessionDaoSupport是如何运作的呢 ...

  2. 你想建设一个能承受500万PV/每天的网站吗?

    (如果感觉有帮助,请帮忙点推荐,添加关注,谢谢!你的支持是我不断更新文章的动力.本博客会逐步推出一系列的关于大型网站架构.分布式应用.设计模式.架构模式等方面的系列文章) 你想建设一个能承受500万P ...

  3. JavaScript DOM高级程序设计 3.-DOM2和HTML2--我要坚持到底!

    由一个HTML进行说明,我就不敲了,直接copy <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" " ...

  4. C# List中写出LINQ类似SQL的语句

    很多时候,从一个关系表中挑出一个我们需要的元素列表采用SQL语句是再容易不过的了,其实C#的List中也可以采用类似的方法,虽然List中集成了Select(), Where()等语句,不过如果你的判 ...

  5. 【HDOJ】2890 Longest Repeated subsequence

    后缀数组的应用.和男人八题那个后缀数组差不多. /* 2890 */ #include <iostream> #include <sstream> #include <s ...

  6. poj 3274 Gold Balanced Lineup(哈希 )

    题目:http://poj.org/problem?id=3274 #include <iostream> #include<cstdio> #include<cstri ...

  7. No Hibernate Session bound to thread, and configuration does not allow

    今天晚上挺悲催的,遇到了这个问题花费我很长时间,现在总结如下: 到这这种情况的发生有两种情况: 1,没有配置事物只要在Spring配置文件中添加如下代码: <bean id="txMa ...

  8. 函数hash_get_nth_cell

    /************************************************************//** Gets the nth cell in a hash table. ...

  9. HAOI2006受欢迎的牛

    求出强联通分量之后判断出度为0的点有几个,有1个就输出这个分量的点的数目,否则输出0: var i,j,n,m,x,y,ans1,ans2,t,cnt,top:longint; head,next,g ...

  10. PHPUNIT 单元测试

    在windows上的安装可以参考其手册 首先下载phpunit.phar文件 1. 为php的二进制可执行文件建立 一个目录,如C:\bin 2. 将C:\bin添加到系统环境变量中, 3. 打开命令 ...