- 浏览: 3053420 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
(Disclaimer:如果需要转载请先与我联系;文中图片请不要直接链接
作者:RednaxelaFX at rednaxelafx.iteye.com)
系列文章:
LINQ与DLR的Expression tree(1): 简介LINQ与Expression tree
LINQ与DLR的Expression tree(2): 简介DLR
LINQ与DLR的Expression tree(3): LINQ与DLR及另外两个库的AST对比
LINQ与DLR的Expression tree(4): 创建静态类型的LINQ表达式树节点
LINQ与DLR的Expression tree(5): 用lambda表达式表示常见控制结构
以前在这边也发过关于LINQ的帖,不过并没有涉及比较深入的部分。之前的这篇:使用lambda expression来动态获取delegate,然后用Cecil注入代码(1)并不是没有下文了,而是在短短的时间内情况发生了许多变化,使得一些原本的想法有机会以更简单的方式来完成。接下来的一段时间我会发一些与LINQ、DLR等相关的帖,探讨它们与动态代码生成、动态类型代码等的关系,也将会继续讨论通过它们来进行依赖注入的话题。
关于这个“树”的叫法……LINQ里的表达式树有官方文档就叫Expression tree,但DLR中相应的语法树却没有什么固定的正式叫法,有时候叫做DLR AST,有时候叫DLR tree。本文(以及接下来的文章)都将使用DLR tree来称呼DLR中的语法树。
LINQ与Expression tree
那么,先简单的说明LINQ与Expression tree的关系。LINQ(Language-INtegrated Query)是.NET Framework 3.5新引入的技术,使得对数据集合的操作能够以统一的API实现。其中,.NET Framework自带的LINQ实现有LINQ-to-Objects、LINQ-to-DataSet、LINQ-to-SQL、LINQ-to-XML等实现。它依赖于.NET Framework 3.5引入的另外几项技术,包括扩展方法(extension method)、lambda表达式(lambda expression)、匿名类型(anonymous type)、自动实现的属性(auto-implemented property)、局部变量类型推导(local variable type inference)等。表达式树(Expression tree)则是LINQ本身的重要组成部分之一。
引用中文MSDN上的文档:
(啊哈,原来微软官方的文档是把Expression tree翻译为“表达式目录树”的啊,以前还一直不知道呢。总之我还是继续用英文标识避免歧义。)
Expression tree、委托与lambda表达式
委托(delegate)与lambda表达式
了解过C# 3.0的新特性的话应该知道,在C# 3.0中新引入了一个语法结构,称为lambda expression(lambda表达式/匿名函数)。对此尚不了解的也可以到MSDN上看看,Lambda表达式。Lambda表达式既可以赋值给一个委托(delegate)类型,例如Action、Func等系列的内建委托类型;也可以赋值给Expression<TDelegate>类型,例如以下lambda表达式:
当它被直接赋值给Func<int, int>类型的变量时,C#编译器会将它的内容编译为一个静态方法,并创建一个对应类型的引用赋值给变量。也就是说,对于
C#编译器会编译为类似下面的代码:
(实际上还涉及到缓存那个委托,这里省略掉了。另外,之所以会编译为一个静态方法是因为这个lambda表达式没有使用任何“自由变量”,也就是既不是参数或局部变量也不是类的成员的变量。在现有的C#编译器实现中,如果一个匿名函数使用了“this”,那么对应生成的方法会是成员方法;如果使用了其它自由变量的话则会生成一个私有内部类来存放匿名函数所使用到的自由变量,并在这个内部类里生成匿名函数对应的方法。这里作为例子选择了最简单的情况来介绍。)
如此将一个lambda表达式编译为一个实际的函数后,其中的MSIL字节码可以为CLR所理解并执行。这样就足够实现in-memory query了,例如LINQ-to-Objects、LINQ-to-DataSet等。但其它平台无法理解MSIL,要对函数进行分析然后执行就会十分困难。例如说,如果想让一个lambda表达式在SQL Server上执行,该如何让SQL Server也理解它呢?
Expression tree与lambda表达式
MSIL之所以不便于分析是因为它将原本是树状结构的程序代码转换为了线性结构,损失了一些信息,主要是损失了程序代码的“结构性”,更接近于底层而降低了抽象程度。
我们知道,程序源代码对应着具体语法树(concrete syntax tree),每个叶节点对应着代码里的一个词素,其上则是各种语法结构,如表达式、语句、声明、定义等。抽象语法树(abstract syntax tree,AST)则在具体语法树的基础上将一些诸如关键字、括号等冗余信息去掉,让树更加整洁,便于分析而不损失任何有用的信息。
前面那个简单的lambda表达式,其对应的具体语法树如下图:
(Expression到Unary Expression中间的虚线表示中间省略了许多层。从Expression到最后的id,中间包括expression -> non-assignment-expression -> conditional-expression -> null-coalescing-expression -> conditional-or-expression -> conditional-and-expression -> inclusive-or-expression -> exclusive-or-expression -> and-expression -> equality-expression -> relational-expression -> additive-expression -> multiplicative-expression -> unary-expression -> - unary-expression -> primary-expression -> primary-no-array-creation-expression -> simple-name -> identifier。要是画到图上太乱了……)
由于具体语法树忠实的对应着原本的语法定义中的各种语法结构,这棵具体语法树中不但含有用于标识lambda表达式的箭头(“=>”),用于标识取反的一元表达式的负号(“-”),还有一堆中间为了表示表达式优先级而设置的层次(也就是省略掉的部分)。这样的语法树上有着太多的冗余信息,原本很简单的代码却变成了复杂的语法树,反而不便于分析和使用了。
既然知道这个表达式一定是一个lambda表达式,那个箭头就可以去掉;得知方法体是一个取反的一元表达式之后,负号也可以去掉;用于表示中间层次的表达式层次也全部可以去掉。将冗余信息抽取掉之后,可以得到一个等价的抽象语法树(AST):
(这幅图的解释在这里有更新)
很好,这样简单多了,而且实际有用的语法结构都保留了下来。这个AST实际上就与LINQ Expression tree的结构对应上了:
换言之,回到代码上,如果像下面这段代码把lambda表达式“x => -x”赋值给一个Expression<Func<int,int>>类型的变量:
编译器会检测到赋值目标是Expression<TDelegate>,于是并不直接生成其对应的静态方法的MSIL,而是生成创建Expression tree的代码。在C#编译器的帮助下,上面的代码完全等价于以下版本:
留意到第二个版本中是如何手工创建Expression tree的,只有这一步不同而已;将expression tree编译为委托类型,然后调用委托的部分是完全一样的。
LINQ的Expression<TDelegate>与其对应的TDelegate委托类型之间的关系是:
1、同一个lambda表达式既可以赋值给Expression<TDelegate>类型的变量也可以赋值给TDelegate类型的委托变量;
2、一个Expression<TDelegate>可以通过调用其Compile<TDelegate>()转换为一个TDelegate类型的委托(反之则不行)。
LINQ的IQueryable<T>与Expression tree
上面介绍了Expression tree与委托的关系,但是乍一看这跟LINQ的联系却不明显。
需要知道,在C# 3/VB 9里的查询表达式实际上会被翻译为针对IEnumerable<T>或者IQueryable<T>的扩展方法;这些扩展方法接收的参数就包括了Func<>系列的委托和Expression<TDelegate>。
举个简单的例子,这个C# 3的查询表达式:
会被翻译为:
.NET Framework中的数组由Array类实现,而它实现了IEnumerable<T>接口,所以可以直接通过Enumerable提供的扩展方法用于LINQ查询。上面的例子就是一个典型的LINQ-to-Objects的应用。
针对IEnumerable<T>的LINQ扩展方法大都接受Func<>系的委托作为参数,例如上面的Where<T>( this IEnumerable<T>, Func<T, bool> ),OrderByDescending<TSource,TKey>( this IEnumerable<TSource>, Func<TSource,TKey> ),Select<TSource,TResult>( this IEnumerable<TSource>, Func<TSource,TResult> )等。于是,作为实际参数传进去的lambda表达式就被编译为了委托。
但如果把例子稍微改动一下,使用IQueryable<T>系的方法的话:(通过AsQueryable()方法可以将一个IEnumerable<T>转换为IQueryable<T>来使用)
会被翻译为:
这里针对IQueryable<T>(IEnumerable<T>的一个子接口)的扩展方法却都变成接收Expression<Func<>>系的Expression tree作为参数了;对应上面的几个方法的signature,只要在Func<>外面包装一层Expression<>就是IQueryable<T>版LINQ查询用的扩展方法的signature。至于为什么要用Expression tree,前面提到过,MSIL不便于在.NET以外的平台上分析和执行,所以委托不合适;Expression tree保留了更多的结构信息,抽象层次比MSIL高,相对来说更适合这项工作。在一个LINQ-to-SQL的查询中,其IQueryProvider会负责将Expression tree优化并转换到SQL,再交给后台的数据库来执行,等获得了执行结果后将执行结果返回给调用者。有了这个途径,LINQ就能与许多平台连接起来,为.NET上的程序对多种平台的数据访问提供统一的访问方式/API。
创建Expression tree的节点
简单介绍过LINQ中的Expression tree的概念后,让我们来讨论一下这棵树里面的节点该如何创建。LINQ Expression tree中的节点一般是通过System.Linq.Expression.Expression这个类上的工厂方法来创建的。包括以下的一些方法:
(方法名:返回值)
上述列表中每个名字其实对应着一组重载,参数不同,但返回值的类型是一样的。这些工厂方法所生成的表达式的意义应该说还是很直观的,就不特别介绍了。经常跟编译器打交道的话应该对这些名字尤其熟悉。
这些工厂方法对应的类的层次结构是:
观察可得知,LINQ Expression tree中的类只能用于表现表达式,而无法表现“更大”的语法结构,例如赋值表达式、一般语句等。这是LINQ v1的一个重要限制,也是为什么C# 3.0的规范里明确提到一个能赋值给Expressin<TDelegate>类型的lambda表达式的方法体只能是一个表达式,而不能是一个代码块——代码块无法通过LINQ v1里的Expression tree表示。同时也值得注意的是,LINQ Expression tree本质上是一棵静态类型的树——所有节点所表示的类型都能够在编译时确定。这一点在后面提到DLR时会再展开讨论。
修改Expression tree
一棵Expession tree在创建后就不可再改变。假如某个程序接收一棵Expression tree为参数,然后仅仅是用于生成别的形式的代码(例如SQL语句),那么这个不可改变性不会有什么影响。但如果一个程序想对一棵Expression tree进行修改该怎么办呢?解决方法是从来源的Expression tree复制出一棵新的树,在复制过程中根据自己的需要选择是直接复制原有节点还是创建修改了的节点。MSDN上有一个例子解释了如何实现这种需求,如何:修改表达式目录树。
更详细的LINQ Expression tree的讨论留待以后再说。下一篇将简单介绍DLR的状况,以及它与LINQ Expression tree的关系。
P.S. 这篇文发了之后才发现上个月博客园的TerryLee也写了类似的介绍,在这里:打造自己的LINQ Provider(上):Expression Tree揭秘,值得一读。
===========================================================================
文中的两幅树状图是用Graphviz的dot来绘制的,代码如下:
cst.dot:
ast.dot:
作者:RednaxelaFX at rednaxelafx.iteye.com)
系列文章:
LINQ与DLR的Expression tree(1): 简介LINQ与Expression tree
LINQ与DLR的Expression tree(2): 简介DLR
LINQ与DLR的Expression tree(3): LINQ与DLR及另外两个库的AST对比
LINQ与DLR的Expression tree(4): 创建静态类型的LINQ表达式树节点
LINQ与DLR的Expression tree(5): 用lambda表达式表示常见控制结构
以前在这边也发过关于LINQ的帖,不过并没有涉及比较深入的部分。之前的这篇:使用lambda expression来动态获取delegate,然后用Cecil注入代码(1)并不是没有下文了,而是在短短的时间内情况发生了许多变化,使得一些原本的想法有机会以更简单的方式来完成。接下来的一段时间我会发一些与LINQ、DLR等相关的帖,探讨它们与动态代码生成、动态类型代码等的关系,也将会继续讨论通过它们来进行依赖注入的话题。
关于这个“树”的叫法……LINQ里的表达式树有官方文档就叫Expression tree,但DLR中相应的语法树却没有什么固定的正式叫法,有时候叫做DLR AST,有时候叫DLR tree。本文(以及接下来的文章)都将使用DLR tree来称呼DLR中的语法树。
LINQ与Expression tree
那么,先简单的说明LINQ与Expression tree的关系。LINQ(Language-INtegrated Query)是.NET Framework 3.5新引入的技术,使得对数据集合的操作能够以统一的API实现。其中,.NET Framework自带的LINQ实现有LINQ-to-Objects、LINQ-to-DataSet、LINQ-to-SQL、LINQ-to-XML等实现。它依赖于.NET Framework 3.5引入的另外几项技术,包括扩展方法(extension method)、lambda表达式(lambda expression)、匿名类型(anonymous type)、自动实现的属性(auto-implemented property)、局部变量类型推导(local variable type inference)等。表达式树(Expression tree)则是LINQ本身的重要组成部分之一。
引用中文MSDN上的文档:
引用
语言集成查询 (LINQ)
LINQ 中的表达式目录树
在 LINQ 中,表达式目录树用于表示针对数据源的结构化查询,这些数据源实现 IQueryable<T>。例如,LINQ to SQL 提供程序实现 IQueryable<T> 接口,用于查询关系数据存储。C# 和 Visual Basic 编译器会将针对此类数据源的查询编译为代码,该代码在运行时将生成一个表达式目录树。然后,查询提供程序可以遍历表达式目录树数据结构,并将其转换为适合于数据源的查询语言。
表达式目录树还可以用在 LINQ 中,用于表示分配给类型为 Expression<TDelegate> 的变量的 Lambda 表达式。
表达式目录树还可用于创建动态 LINQ 查询。有关更多信息,请参见如何:使用表达式目录树来生成动态查询。如果要生成 LINQ 提供程序,您也可以使用表达式目录树。有关更多信息,请参见演练:创建 IQueryable LINQ 提供程序。
LINQ 中的表达式目录树
在 LINQ 中,表达式目录树用于表示针对数据源的结构化查询,这些数据源实现 IQueryable<T>。例如,LINQ to SQL 提供程序实现 IQueryable<T> 接口,用于查询关系数据存储。C# 和 Visual Basic 编译器会将针对此类数据源的查询编译为代码,该代码在运行时将生成一个表达式目录树。然后,查询提供程序可以遍历表达式目录树数据结构,并将其转换为适合于数据源的查询语言。
表达式目录树还可以用在 LINQ 中,用于表示分配给类型为 Expression<TDelegate> 的变量的 Lambda 表达式。
表达式目录树还可用于创建动态 LINQ 查询。有关更多信息,请参见如何:使用表达式目录树来生成动态查询。如果要生成 LINQ 提供程序,您也可以使用表达式目录树。有关更多信息,请参见演练:创建 IQueryable LINQ 提供程序。
(啊哈,原来微软官方的文档是把Expression tree翻译为“表达式目录树”的啊,以前还一直不知道呢。总之我还是继续用英文标识避免歧义。)
Expression tree、委托与lambda表达式
委托(delegate)与lambda表达式
了解过C# 3.0的新特性的话应该知道,在C# 3.0中新引入了一个语法结构,称为lambda expression(lambda表达式/匿名函数)。对此尚不了解的也可以到MSDN上看看,Lambda表达式。Lambda表达式既可以赋值给一个委托(delegate)类型,例如Action、Func等系列的内建委托类型;也可以赋值给Expression<TDelegate>类型,例如以下lambda表达式:
x => -x
当它被直接赋值给Func<int, int>类型的变量时,C#编译器会将它的内容编译为一个静态方法,并创建一个对应类型的引用赋值给变量。也就是说,对于
static class Program { static void Main( string[ ] args ) { Func<int, int> negateFunc = x => -x; } }
C#编译器会编译为类似下面的代码:
internal static class Program [CompilerGenerated] private static int <Main>b__0( int x ) { return -x; } private static void Main( string[ ] args ) { Func<int, int> negateFunc = new Func<int, int>( <Main>b__0 ); } }
(实际上还涉及到缓存那个委托,这里省略掉了。另外,之所以会编译为一个静态方法是因为这个lambda表达式没有使用任何“自由变量”,也就是既不是参数或局部变量也不是类的成员的变量。在现有的C#编译器实现中,如果一个匿名函数使用了“this”,那么对应生成的方法会是成员方法;如果使用了其它自由变量的话则会生成一个私有内部类来存放匿名函数所使用到的自由变量,并在这个内部类里生成匿名函数对应的方法。这里作为例子选择了最简单的情况来介绍。)
如此将一个lambda表达式编译为一个实际的函数后,其中的MSIL字节码可以为CLR所理解并执行。这样就足够实现in-memory query了,例如LINQ-to-Objects、LINQ-to-DataSet等。但其它平台无法理解MSIL,要对函数进行分析然后执行就会十分困难。例如说,如果想让一个lambda表达式在SQL Server上执行,该如何让SQL Server也理解它呢?
Expression tree与lambda表达式
MSIL之所以不便于分析是因为它将原本是树状结构的程序代码转换为了线性结构,损失了一些信息,主要是损失了程序代码的“结构性”,更接近于底层而降低了抽象程度。
我们知道,程序源代码对应着具体语法树(concrete syntax tree),每个叶节点对应着代码里的一个词素,其上则是各种语法结构,如表达式、语句、声明、定义等。抽象语法树(abstract syntax tree,AST)则在具体语法树的基础上将一些诸如关键字、括号等冗余信息去掉,让树更加整洁,便于分析而不损失任何有用的信息。
前面那个简单的lambda表达式,其对应的具体语法树如下图:
(Expression到Unary Expression中间的虚线表示中间省略了许多层。从Expression到最后的id,中间包括expression -> non-assignment-expression -> conditional-expression -> null-coalescing-expression -> conditional-or-expression -> conditional-and-expression -> inclusive-or-expression -> exclusive-or-expression -> and-expression -> equality-expression -> relational-expression -> additive-expression -> multiplicative-expression -> unary-expression -> - unary-expression -> primary-expression -> primary-no-array-creation-expression -> simple-name -> identifier。要是画到图上太乱了……)
由于具体语法树忠实的对应着原本的语法定义中的各种语法结构,这棵具体语法树中不但含有用于标识lambda表达式的箭头(“=>”),用于标识取反的一元表达式的负号(“-”),还有一堆中间为了表示表达式优先级而设置的层次(也就是省略掉的部分)。这样的语法树上有着太多的冗余信息,原本很简单的代码却变成了复杂的语法树,反而不便于分析和使用了。
既然知道这个表达式一定是一个lambda表达式,那个箭头就可以去掉;得知方法体是一个取反的一元表达式之后,负号也可以去掉;用于表示中间层次的表达式层次也全部可以去掉。将冗余信息抽取掉之后,可以得到一个等价的抽象语法树(AST):
(这幅图的解释在这里有更新)
很好,这样简单多了,而且实际有用的语法结构都保留了下来。这个AST实际上就与LINQ Expression tree的结构对应上了:
LambdaExpression UnaryExpression (body; negation) ParameterExpression ("x") ParameterExpression (parameter; "x")
换言之,回到代码上,如果像下面这段代码把lambda表达式“x => -x”赋值给一个Expression<Func<int,int>>类型的变量:
using System; using System.Linq.Expressions; static class Program { static void Main( string[ ] args ) { Expression<Func<int, int>> negateExpr = x => -x; Func<int, int> negateFunc = negateExpr.Compile( ); Console.WriteLine( negateFunc( 1 ) ); } }
编译器会检测到赋值目标是Expression<TDelegate>,于是并不直接生成其对应的静态方法的MSIL,而是生成创建Expression tree的代码。在C#编译器的帮助下,上面的代码完全等价于以下版本:
using System; using System.Linq.Expressions; static class Program { static void Main( string[ ] args ) { ParameterExpression param = Expression.Parameter(typeof(int), "x"); Expression<Func<int, int>> negateExpr = Expression.Lambda<Func<int, int>>( Expression.Negate( param ), new ParameterExpression[ ] { param } ); Func<int, int> negateFunc = negateExpr.Compile( ); Console.WriteLine( negateFunc( 1 ) ); } }
留意到第二个版本中是如何手工创建Expression tree的,只有这一步不同而已;将expression tree编译为委托类型,然后调用委托的部分是完全一样的。
LINQ的Expression<TDelegate>与其对应的TDelegate委托类型之间的关系是:
1、同一个lambda表达式既可以赋值给Expression<TDelegate>类型的变量也可以赋值给TDelegate类型的委托变量;
2、一个Expression<TDelegate>可以通过调用其Compile<TDelegate>()转换为一个TDelegate类型的委托(反之则不行)。
LINQ的IQueryable<T>与Expression tree
上面介绍了Expression tree与委托的关系,但是乍一看这跟LINQ的联系却不明显。
需要知道,在C# 3/VB 9里的查询表达式实际上会被翻译为针对IEnumerable<T>或者IQueryable<T>的扩展方法;这些扩展方法接收的参数就包括了Func<>系列的委托和Expression<TDelegate>。
举个简单的例子,这个C# 3的查询表达式:
from s in new [ ] { "a", "b", "cd", "efg" } where s.Length == 1 orderby s descending select "--" + s + "--"
会被翻译为:
( new [ ] { "a", "b", "cd", "efg" } ) .Where( s => s.Length == 1 ) .OrderByDescending( s => s ) .Select( s => "--" + s + "--" )
.NET Framework中的数组由Array类实现,而它实现了IEnumerable<T>接口,所以可以直接通过Enumerable提供的扩展方法用于LINQ查询。上面的例子就是一个典型的LINQ-to-Objects的应用。
针对IEnumerable<T>的LINQ扩展方法大都接受Func<>系的委托作为参数,例如上面的Where<T>( this IEnumerable<T>, Func<T, bool> ),OrderByDescending<TSource,TKey>( this IEnumerable<TSource>, Func<TSource,TKey> ),Select<TSource,TResult>( this IEnumerable<TSource>, Func<TSource,TResult> )等。于是,作为实际参数传进去的lambda表达式就被编译为了委托。
但如果把例子稍微改动一下,使用IQueryable<T>系的方法的话:(通过AsQueryable()方法可以将一个IEnumerable<T>转换为IQueryable<T>来使用)
from s in ( new [ ] { "a", "b", "cd", "efg" } ).AsQueryable( ) where s.Length == 1 orderby s descending select "--" + s + "--"
会被翻译为:
( new [ ] { "a", "b", "cd", "efg" } ).AsQueryable( ) .Where( s => s.Length == 1 ) .OrderByDescending( s => s ) .Select( s => "--" + s + "--" )
这里针对IQueryable<T>(IEnumerable<T>的一个子接口)的扩展方法却都变成接收Expression<Func<>>系的Expression tree作为参数了;对应上面的几个方法的signature,只要在Func<>外面包装一层Expression<>就是IQueryable<T>版LINQ查询用的扩展方法的signature。至于为什么要用Expression tree,前面提到过,MSIL不便于在.NET以外的平台上分析和执行,所以委托不合适;Expression tree保留了更多的结构信息,抽象层次比MSIL高,相对来说更适合这项工作。在一个LINQ-to-SQL的查询中,其IQueryProvider会负责将Expression tree优化并转换到SQL,再交给后台的数据库来执行,等获得了执行结果后将执行结果返回给调用者。有了这个途径,LINQ就能与许多平台连接起来,为.NET上的程序对多种平台的数据访问提供统一的访问方式/API。
创建Expression tree的节点
简单介绍过LINQ中的Expression tree的概念后,让我们来讨论一下这棵树里面的节点该如何创建。LINQ Expression tree中的节点一般是通过System.Linq.Expression.Expression这个类上的工厂方法来创建的。包括以下的一些方法:
(方法名:返回值)
Add : BinaryExpression AddChecked : BinaryExpression And : BinaryExpression AndAlso : BinaryExpression ArrayIndex : BinaryExpression ArrayLength : UnaryExpression Call : MethodCallExpression Coalesce : BinaryExpression Condition : ConditionalExpression Constant : ConstantExpression Convert : UnaryExpression ConvertChecked : UnaryExpression Divide : BinaryExpression Equal : BinaryExpression ExclusiveOr : BinaryExpression Field : MemberExpression GreaterThan : BinaryExpression GreaterThanOrEqual : BinaryExpression Invoke : InvocationExpression Lambda : Expression<TDelegate> LeftShift : BinaryExpression LessThan : BinaryExpression LessThanOrEqual : BinaryExpression ListInit : ListInitExpression MakeBinary : BinaryExpression MakeMemberAccess : MemberExpression MakeUnary : UnaryExpression MemberInit : MemberInitExpression Modulo : BinaryExpression Multiply : BinaryExpression MultiplyChecked : BinaryExpression Negate : UnaryExpression NegateChecked : UnaryExpression New : NewExpression NewArrayBounds : NewArrayExpression NewArrayInit : NewArrayExpression Not : UnaryExpression NotEqual : BinaryExpression Or : BinaryExpression OrElse : BinaryExpression Parameter : ParameterExpression Power : BinaryExpression Property : MemberExpression PropertyOrField : MemberExpression Quote : UnaryExpression RightShift : BinaryExpression Subtract : BinaryExpression SubtractChecked : BinaryExpression TypeAs : UnaryExpression TypeIs : TypeBinaryExpression UnaryPlus : UnaryExpression
上述列表中每个名字其实对应着一组重载,参数不同,但返回值的类型是一样的。这些工厂方法所生成的表达式的意义应该说还是很直观的,就不特别介绍了。经常跟编译器打交道的话应该对这些名字尤其熟悉。
这些工厂方法对应的类的层次结构是:
System.Object System.Linq.Expressions.Expression System.Linq.Expressions.BinaryExpression System.Linq.Expressions.ConditionalExpression System.Linq.Expressions.ConstantExpression System.Linq.Expressions.InvocationExpression System.Linq.Expressions.LambdaExpression System.Linq.Expressions.Expression<TDelegate> System.Linq.Expressions.MemberExpression System.Linq.Expressions.MethodCallExpression System.Linq.Expressions.NewExpression System.Linq.Expressions.NewArrayExpression System.Linq.Expressions.MemberInitExpression System.Linq.Expressions.ListInitExpression System.Linq.Expressions.ParameterExpression System.Linq.Expressions.TypeBinaryExpression System.Linq.Expressions.UnaryExpression
观察可得知,LINQ Expression tree中的类只能用于表现表达式,而无法表现“更大”的语法结构,例如赋值表达式、一般语句等。这是LINQ v1的一个重要限制,也是为什么C# 3.0的规范里明确提到一个能赋值给Expressin<TDelegate>类型的lambda表达式的方法体只能是一个表达式,而不能是一个代码块——代码块无法通过LINQ v1里的Expression tree表示。同时也值得注意的是,LINQ Expression tree本质上是一棵静态类型的树——所有节点所表示的类型都能够在编译时确定。这一点在后面提到DLR时会再展开讨论。
修改Expression tree
一棵Expession tree在创建后就不可再改变。假如某个程序接收一棵Expression tree为参数,然后仅仅是用于生成别的形式的代码(例如SQL语句),那么这个不可改变性不会有什么影响。但如果一个程序想对一棵Expression tree进行修改该怎么办呢?解决方法是从来源的Expression tree复制出一棵新的树,在复制过程中根据自己的需要选择是直接复制原有节点还是创建修改了的节点。MSDN上有一个例子解释了如何实现这种需求,如何:修改表达式目录树。
更详细的LINQ Expression tree的讨论留待以后再说。下一篇将简单介绍DLR的状况,以及它与LINQ Expression tree的关系。
P.S. 这篇文发了之后才发现上个月博客园的TerryLee也写了类似的介绍,在这里:打造自己的LINQ Provider(上):Expression Tree揭秘,值得一读。
===========================================================================
文中的两幅树状图是用Graphviz的dot来绘制的,代码如下:
cst.dot:
digraph ExpressionTree { node [fontsize=12, fontcolor=blue, font=Courier, shape=box] // node declarations lambda [label="Lambda Expression"] anoFuncSig [label="Anonymous Function\nSignature"] arrow [label="=>"] anoFuncBody [label="Anonymous Function\nBody"] impFuncSig [label="Implicit Anonymous\nFunction Signature"] expr [label="Expression"] impParam [label="Implicit Anonymous\nFunction Parameter"] uexpr1 [label="Unary Expression"] neg [label="-"] uexpr2 [label="Unary Expression"] id [label="Identifier:\nx"] simpName [label="Simple Name:\nx"] // relations lambda -> anoFuncSig lambda -> arrow lambda -> anoFuncBody {rank=same; anoFuncSig arrow anoFuncBody } anoFuncSig -> impFuncSig anoFuncBody -> expr {rank=same; impFuncSig expr } impFuncSig -> impParam expr -> uexpr1 [style=dashed] {rank=same; impParam uexpr1 } impParam -> id uexpr1 -> neg uexpr1 -> uexpr2 {rank=same; id neg uexpr2 } uexpr2 -> simpName }
ast.dot:
digraph ExpressionTree { node [fontsize=12, fontcolor=blue, font=Courier, shape=box] edge [fontsize=10, fontcolor=purple] // node declarations lambda [label="Lambda Expression"] param [label="Parameter:\nx"] body [label="Unary Expression\n(Negation)"] param2 [label="Simple Name:\nx"] // relations lambda -> param [label="Signature"] lambda -> body [label="Body"] {rank=same; param body } body -> param2 param -> param2 [label="(same node)", fontsize=8, style=dashed, dir=both] }
评论
5 楼
hd700
2008-09-29
博主加油!
4 楼
RednaxelaFX
2008-09-21
多谢关注 ^ ^
现在的LINQ Expression tree没办法表达ref或者out参数,不过多语句的方法体经过一定变换是有可能表达的;变换后就不再是多语句,而变成一个表达式了。当然,有ref和out参数的方法是没办法变换的;但有副作用的方法却未必变换不了,得看情况。
我已经写了相关的内容,在这个系列文章的4和5里。问题是4和5虽然写完了,3却还没写完,所以还得等一阵子才会发出来。不介意的话请等那些部分出来再过来看看?
P.S. 下一个版本的LINQ Expression tree应该就有办法直接表达语句的概念了,所以有很多现在很麻烦或者做不到的事情等下一版本就能很轻松的做到。
hd700 写道
想请教一下,ExpreesionTree怎么能表达过程式编程写多条语句,例如:
public int Test(int a) { this.Inc(ref a); return a; }
现在的LINQ Expression tree没办法表达ref或者out参数,不过多语句的方法体经过一定变换是有可能表达的;变换后就不再是多语句,而变成一个表达式了。当然,有ref和out参数的方法是没办法变换的;但有副作用的方法却未必变换不了,得看情况。
我已经写了相关的内容,在这个系列文章的4和5里。问题是4和5虽然写完了,3却还没写完,所以还得等一阵子才会发出来。不介意的话请等那些部分出来再过来看看?
P.S. 下一个版本的LINQ Expression tree应该就有办法直接表达语句的概念了,所以有很多现在很麻烦或者做不到的事情等下一版本就能很轻松的做到。
3 楼
hd700
2008-09-21
想请教一下,ExpreesionTree怎么能表达过程式编程写多条语句,例如:
public int Test(int a)
{
this.Inc(ref a);
return a;
}
public int Test(int a)
{
this.Inc(ref a);
return a;
}
2 楼
RednaxelaFX
2008-09-14
这样么……嗯有道理,我虽然懒得维护多个blog,不过到那边去发似乎更有用一些
多谢建议 ^ ^
多谢建议 ^ ^
1 楼
sdhjl2000
2008-09-14
呵呵,看来javaeye里也有.net先锋啊,不过应该发到园子里这样才有更多人研究讨论
发表评论
-
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22410(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对象的重量
2011-08-21 17:15 0http://domino.research.ibm.com/ ... -
IronRuby 1.1系的自适应执行(解释/编译的混合模式)
2010-10-29 14:12 0IronRuby自身的compiler部分基本上还是保持不变的 ... -
Expression Tree中的Constant被编译后放到哪里去了?
2010-02-28 16:21 0Expression.Constant()可以放任意对象进去作 ... -
GetCustomAttribute()每次都返回新Attribute实例
2009-11-10 10:30 0Jeffrey Zhao: 一次失败的尝试(上):原来GetC ... -
拿ETv2来生成方法体的两种阳春办法
2009-09-22 06:03 0System.Type System.Reflection.E ... -
委托与方法和隐藏参数
2009-09-07 15:32 3312之前正好发了些帖子是关于CLR里的委托的,然后看到老赵说事件也 ... -
要让CLR挂掉的话(第二弹)……
2009-09-04 03:26 12880(Disclaimer:如果需要转 ... -
要让CLR挂掉的话……
2009-09-02 16:53 4786(Disclaimer:如果需要转载请先与我联系。 作者:Re ... -
趣味编程:函数式链表的快速排序
2009-08-31 08:53 3452(恢复自2009-08-28的备份 ... -
事件处理器导致内存泄漏
2009-08-25 15:03 0Memory leak via event handlers ... -
C# 3.0的类型推导
2009-08-23 12:24 0Howard Dierking: Lambda, Lambda ... -
把lock的意思给弄混了 T T
2009-08-20 17:49 2605悲剧啊……前几天有个同学不停问我Java里的同步问题,今天写C ... -
把IEnumerable<T>和IObservable<T>粘起来?
2009-07-23 03:02 0Channel 9: Expert to Expert: Br ... -
Scott Peterson: Variance, Thy Name is Ambiguity
2009-07-01 23:49 1639原文作者:Scott Peterson 原文地址:http:/ ... -
void无法协变
2009-06-30 11:17 0Eric Lippert The void is invari ... -
同一个表达式算出来的浮点数结果会不相等?
2009-05-30 03:27 0浮点数有很多可把玩的地方。例如下面这段C程序: #includ ... -
C#的语言结构到Expression Tree v2的映射
2009-05-21 03:11 0在.NET Framework 4 Beta 1中,Expre ...
相关推荐
这篇博客文章“LINQ与DLR的Expression tree(4):创建静态类型的LINQ表达式树节点”深入探讨了如何构建这种数据结构,特别是关注静态类型的表达式树节点。 Expression Tree是一种表示方法调用、条件语句、算术运算...
LINQ初体验之LINQ to Object 1 一步一步学Linq to sql(一):预备知识 4 一步一步学Linq to sql(二):DataContext与实体 9 一步一步学Linq to sql(三):增删改 15 一步一步学Linq to sql(四):查询句法 21 ...
LINQ初体验之LINQ to Object 1 一步一步学Linq to sql(一):预备知识 4 一步一步学Linq to sql(二):DataContext与实体 9 一步一步学Linq to sql(三):增删改 15 一步一步学Linq to sql(四):查询句法 21 ...
LINQ中文教程LINQ初体验之LINQ to Object 1 一步一步学Linq to sql(一):预备知识 4 一步一步学Linq to sql(二):DataContext与实体 9 一步一步学Linq to sql(三):增删改 15 一步一步学Linq to sql(四):...
Your Complete Example-Rich Guide to Using and Extending LINQ to Objects and PLINQ Using LINQ to Objects, .NET developers can write queries over object collections with the same deep functionality ...
介绍了System.Linq.Expression命名空间下的Expression类以及如何执行和修改表达式树,最后介绍了表达式树用到的访问者模式。
1. **LINQ - Join Operators**:这部分内容讲解了如何使用JOIN操作符,包括INNER JOIN、LEFT JOIN、RIGHT JOIN和FULL JOIN,它们在处理多表关联查询时非常有用,能方便地将来自不同数据源的信息合并。 2. **LINQ - ...
5. **SQL优化**:针对与数据库交互的查询,`Linq.Expression.Optimizer`还能够对SQL语句进行优化,如消除无效的JOIN操作,简化WHERE子句等。 值得注意的是,尽管`Linq.Expression.Optimizer`提供了强大的优化功能,...
1. **查询语法**:LINQ提供了两种查询语法——查询表达式(Query Expression)和方法链(Method Chain),两者都可以实现相同的功能,但写法不同。 2. **延迟执行**:LINQ查询的执行是延迟的,这意味着查询定义...
LINQ(Language Integrated Query,语言集成查询)是微软公司提供的一项新技术,能够将查询功能直接引入到.NET Framework 3.5所支持的编程语言中,如C#、***等。通过LINQ,查询操作可以通过编程语言自身来传达,而...
1. **查询表达式(Query Expression)**:LINQ引入了一种新的查询语法,类似于SQL,但完全融入到C#和VB.NET的语言中。例如: ```csharp var query = from student in students where student.Age > 18 select ...
1. **LINQ to SQL**: LINQ to SQL 是一种数据访问技术,它允许开发者使用C#或VB.NET的查询语法直接对SQL Server数据库进行操作。通过DataContext类,开发者可以映射数据库表到.NET对象,从而实现对象关系映射(ORM...
1. **Linq的基本概念**: - **查询表达式(Query Expression)**:一种以关键字`from`、`where`、`select`、`group`等组成的声明式查询语法,与SQL查询类似。 - **方法链(Method Chaining)**:通过调用如`Where...
可能的内容包括:LINQ的引入背景、查询表达式语法、方法语法的使用、与数据库交互的LINQ to SQL、与对象交互的LINQ to Objects以及与XML数据操作的LINQ to XML等。读者通过此书能快速上手并理解LINQ的核心功能。 **...
java8流源码Java ...linq1:哪里 - 简单 1 // c# public void Linq1 () { int [] numbers = { 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 }; var lowNums = from n in numbers where n < 5 select n
1. **Linq(Language Integrated Query,语言集成查询)**:是.NET框架中的一种技术,允许开发人员使用类似于SQL的语法在各种数据源上执行查询,包括集合、数据库、XML等。Linq使得代码更加简洁、可读性更强,并提供...
1. **Linq的基本概念**:了解Linq的核心思想,包括查询表达式和方法调用两种查询方式,以及它们与传统SQL查询的区别。 2. **Linq查询语法**:学习如何使用`from`、`where`、`select`、`group by`等关键字来构建查询...
1. **LINQ基础**:理解查询表达式(query expressions)和方法链(method chaining)两种主要的LINQ语法形式。 2. **数据源操作**:学习如何从TreeView的节点数据绑定到LINQ查询,包括选择、过滤、排序和分组节点。 ...
1. 查询表达式:LINQ的主要特征之一是查询表达式(Query Expression),其语法类似于SQL,但运行在内存中的对象上。例如: ```csharp var query = from student in students where student.Age > 18 select ...
1. **理解LINQ的基本概念**:首先,我们需要了解LINQ的基本构成,包括查询表达式和方法语法,以及它们如何与数据源交互。这包括如何定义查询变量、使用查询运算符(如Where、Select、GroupBy等)以及如何执行查询。 ...