概述
在.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: nop IL_0001: ldsfld class [System.Core]System.Func`3<int32,int32,int32> TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1' IL_0006: brtrue.s IL_001b IL_0008: ldnull IL_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_001b IL_001b: ldsfld class [System.Core]System.Func`3<int32,int32,int32> TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1' IL_0020: stloc.0 IL_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: nop IL_0001: ldtoken [mscorlib]System.Int32 IL_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.2 IL_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()); }
访问与修改表达式目录树
static void Main(string[] args) { Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2; Console.WriteLine(lambda.ToString()); }
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()); }
为什么需要表达式目录树
总结
本文出自 “TerryLee技术专栏” 博客,请务必保留此出处http://terrylee.blog.51cto.com/342737/90559