刀具东莞网站建设,计算机网络技术就业方向专科,网页制作模板的百度注册中知识,南宁市建设工程质量监督站网站概述 在.NET Framework 3.5中提供了LINQ 支持后#xff0c;LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱#xff0c;而各种LINQ Provider更是满天飞#xff0c;如LINQ to NHibernate、LINQ to Google等#xff0c;大有“一切皆LINQ”的趋势。LINQ本身也提供了很好的…概述 在.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表达式看下面的代码 ExpressionFuncint, int, int expression (a, b) a * b 2; 在我们将Lambda表达式指定给ExpressionTDelegate类型的变量参数时编译器将会发出生成表达式目录树的指令如上面这段代码中的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而泛型的ExpressionTDelegate则继承于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.LambdaFuncint, int, int(binaryBody, paraLeft, paraRight);Console.WriteLine(lambda.ToString());Console.Read();
} 这里构造的表达式目录树仍然如下图所示 运行这段代码看看输出了什么 可以看到通过手工构造的方式我们确实构造出了同前面一样的Lambda表达式。对于一个表达式目录树来说它有几个比较重要的属性 Body指表达式的主体部分 Parameters指表达式的参数 NodeType指表达式的节点类型如在上面的例子中它的节点类型是Lambda Type指表达式的静态类型在上面的例子中Type为Funint,int,int。 在Expression Tree Visualizer中我们可以看到表达式目录树的相关属性如下图所示 表达式目录树与委托 大家可能经常看到如下这样的语言其中第一句是直接用Lambda表达式来初始化了Func委托而第二句则使用Lambda表达式来构造了一个表达式目录树它们之间的区别是什么呢 static void Main(string[] args)
{Funcint, int, int lambda (a, b) a b * 2;ExpressionFuncint, 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.Func3int32,int32,int32 lambda)IL_0000: nopIL_0001: ldsfld class [System.Core]System.Func3int32,int32,int32 TerryLee.LinqToLiveSearch.Program::CS$9__CachedAnonymousMethodDelegate1IL_0006: brtrue.s IL_001bIL_0008: ldnullIL_0009: ldftn int32 TerryLee.LinqToLiveSearch.Program::Mainb__0(int32,int32)IL_000f: newobj instance void class [System.Core]System.Func3int32,int32,int32::.ctor(object,native int)IL_0014: stsfld class [System.Core]System.Func3int32,int32,int32 TerryLee.LinqToLiveSearch.Program::CS$9__CachedAnonymousMethodDelegate1IL_0019: br.s IL_001bIL_001b: ldsfld class [System.Core]System.Func3int32,int32,int32 TerryLee.LinqToLiveSearch.Program::CS$9__CachedAnonymousMethodDelegate1IL_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.Expression1class [System.Core]System.Func3int32,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 aIL_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);ExpressionFuncint, int, int lambda Expression.LambdaFuncint, int, int(binaryBody, paraLeft, paraRight);Funcint, int, int myLambda lambda.Compile();int result myLambda(2, 3);Console.WriteLine(result: result.ToString());Console.Read();
} 运行后输出的结果 这里我们只要简单的调用Compile方法就可以了事实上在.NET Framework中是调用了一个名为ExpressionCompiler的内部类来做表达式目录树的执行注意此处的Compiler不等同于编译器的编译。另外只能执行表示Lambda表达式的表达式目录树即LambdaExpression或者ExpressionTDelegate类型。如果表达式目录树不是表示Lambda表达式需要调用Lambda方法创建一个新的表达式。如下面的代码 static void Main(string[] args)
{BinaryExpression body Expression.Add(Expression.Constant(2),Expression.Constant(3));ExpressionFuncint expression Expression.LambdaFuncint(body, null);Funcint lambda expression.Compile();Console.WriteLine(lambda());
} 访问与修改表达式目录树 在本文一开始我就说过 通过解析表达式目录树我们可以实现一些特定功能既然要解析表达式目录树对于表达式目录树的访问自然是不可避免的。在.NET Framework中提供了一个抽象的表达式目录树访问类ExpressionVisitor但它是一个internal的我们不能直接访问。幸运的是在MSDN中微软给出了ExpressionVisitor类的实现我们可以直接拿来使用。该类是一个抽象类微软旨在让我们在集成ExpressionVisitor的基础上实现自己的表达式目录树访问类。现在我们来看简单的表达式目录树 static void Main(string[] args)
{ExpressionFuncint, 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)
{ExpressionFuncint, 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揭秘 作者李会军.