标签:
表达式树基础
考虑下面简单的Lambda表达式:
当方法调用后,变量c将被设成3+5,即8。
上面声明中第一步委托类型Func是在System命名空间中为我们定义好的:
现在我们可以创建一个表达式树:
图1:VS2008 C# Samples中的ExpressionTreeVisualizer创建一个表达式树的象征性的输出
这段代码产生如下输入:
(a + b)
表达式左边部分: a
节点类型: Add
表达式右边部分: b
类型: System.Int32
同样,你会发现很容易在图1的Body节点中找到这些信息。
为什么要将LINQ to SQL查询表达式转换成表达式树呢?
你已经学习了表达式树是一个用来表示可执行代码的数据结构。但到目前为止我们还没有回答一个核心问题,那就是为什么我们要做这样的转换。这个问题是我们在本文开始时提出来的,现在是时候回答了。
一个LINQ to SQL查询不是在你的C#程序里执行的。相反,它被转换成SQL,通过网络发送,最后在数据库服务器上执行。换句话说,下面的代码实际上从来不会在你的程序里执行:
var query = from c in db.Customers
where c.City == "Nantes"
select new { c.City, c.CompanyName };
它首先被转换成下面的SQL语句然后在服务器上执行:
SELECT [t0].[City], [t0].[CompanyName]
从查询表达式的代码转换成SQL查询语句----它可以通过字符串形式被发送到其他程序。在这里,这个程序恰好是SQL Server数据库。像这样将数据结构转换到SQL显然比直接从原生IL或可执行代码转换到SQL要容易得多。这有些夸大问题的难度,只要试想转换0和1的序列到SQL!
现在是时候将你的查询表达式转换成SQL了,描述查询的表达式树是分解并解析了的,就像我们在上一节分解我们的简单的lambda表达式树一样。当然,解析LINQ to SQL表达式树的算法比我们用的那个要复杂得多,但规则是一样的。一旦解析了表达式树的各部分,那么LINQ开始斟酌以最好的方式生成返回被请求的数据的SQL语句。
表达式树被创建是为了制造一个像将查询表达式转换成字符串以传递给其他程序并在那里执行这样的转换任务。就是这么简单。没有巨大奥秘,不需要挥舞魔杖。只是简单的:把代码,转换成数据,然后分析数据发现其组成部分,最后转换成可以传递到其他程序的字符串。
由于查询来自编译器封装的抽象的数据结构,编译器可以获取任何它想要的信息。它不要求执行查询要在特定的顺序,或用特定的方式。相反,它可以分析表达式树,寻找你要做的是什么,然后再决定怎么去做。至少在理论上,我们可以自由的考虑各种因素,比如网络状况,数据库负载,结果集是否有效,等等。在实际中LINQ to SQL不考虑所有这些因素,但它理论上可以自由的做几乎所有想做的事。此外,人们可以通过表达式树将自己编写的代码,分析并转换成跟LINQ to SQL提供的完全不同的东西。
var query = from method in typeof(System.Linq.Enumerable).GetMethods()
orderby method.Name
group method by method.Name into g
select new { Name = g.Key, Overloads = g.Count() };
概要
本文覆盖了表达式树的一些基本情况。通过将代码转换成数据,这些数据结构揭示并描绘表达式的组成部分。从最小的概念上讲,理解表达式树是相当简单的。它获取可执行表达式并获取其组成部分放入树形数据结构。例如,我们检测这个简单的表达式:
(a,b) => a + b;
通过研究来源于这个表达式的树,你能看到创建树的基本规则,见图1。
你同样可以看到表达式树在LINQ to SQL里扮演非常重要的角色。尤其,他们是LINQ to SQL查询表达式用来获取逻辑的数据抽象。解析并分析此数据得到SQL语句,然后发送到服务器。
LINQ使查询C#语言的一个普通类即有类型检查也有智能感知。其代码是类型检查和智能感知的,必须使用正确的C#语法,它能直接转换到可执行代码,就像任何其他C#代码一样被转换和执行。表达式树使将可执行代码转换成能传递到服务器的SQL语句相对容易。
查询返回IEnumerable<T>优于IQueryable<T>表示不使用表达式树。作为一般性规则,可以这么说:LINQ查询在程序内执行时不需要表达式树,当代码在程序外执行时可以利用表达式树。
==========================================================================
已经有很多很多人聊过这个话题,今天我在这里重复也不会探讨出什么新东西,只是把自己的理解描述出来,更是为了整个系列文章的完整性。
当你听说Linq给你的承诺时,你怎么想的?Wow,我们可以以统一的方式操作各种各样的数据了。这就是我当时的想法。虽然人们在现实中总是喜欢差异,认为差异才能产生美,如果一切的一切都是一样的,这个世界将无比的单调,可是作为程序员的我们却对标准趋之若鹜,对差异嫉恶如仇。看同桌的你是不是正在为了Oracle和Sql Server两种数据库编写两套数据访问的类?
表达式树概念
Linq的承诺貌似Java那个梦想一样:Write Once,Run Anywhere。Java是怎么做到的?Sun等公司为我们在各种平台架构上实现了各自的虚拟机,Java的编译分为两个阶段,第一阶段将Java代码编译为字节码,在这个阶段不管在什么平台上,只要Java源代码一样生成的字节码是一致的,第二个阶段,也就是运行阶段,虚拟机会根据平台的不同生成不同的代码。就是通过将编译器分为前端和后端来实现这个梦想。
实际上LINQ也是这么做的,对各种数据的操作无非“增删查改”,但是具体做的时候关系数据库需要使用SQL来操作,而XML需要XPath来操作。我们如何将“增删查改”的语法做到一致,让我们用起来好像操作的数据只有一种?
答案是使用表达式树
表达式树仅仅是将表达式(这里特指Lambda表达式)用树状的数据结构来表示。相当于Java的字节码,至于如何去解析这个树的结构那就看你自己了,如何去解释这个表达式树。
看下面这个Lambda表达式:
username => username == “yuyi”
我们主要看表达式的主体:username == “yuyi”,如果我们是要对数据库进行操作,这将翻译为字段username中所有值为”yuyi”的行,如果操作的是XML那也许是查询名称为username,值为”yuyi”的attribute。表达式树承载的只是这样一个结构:
如何解释它这是你的事。
在C#中Expression<Function<string,bool>> IsTrue = username=>username==”yuyi”,就表示一个表达式树,这个语句在编译后就组成为一个树状的结构。
在编译原理中,我们知道编译器的前两个阶段主要做的是:词法分析、语法分析。在词法分析中编译器会从代码文件中读入一个个的字符,然后识别出其中的关键字、标识符、运算符、常量、字符串等。
比如上面的表达式就会识别出:
Username->标识符
==->运算符
“yuyi”->字符串
这些东西就叫做符号
然后以这些符号作为输入进行语法分析,语法分析会将这些符号组合成一个树,这个树我们称之为抽象语法树(AST),表达式树也是一种简单的AST。
(在VS2008带的例子中有个DynamicQuery的例子,这里有一个Dynamic.cs文件,这里就自己做表达式的解析,只不过解析的是用字符串形式的表达式,通过这个代码你可以看看表达式的解析过程,这里有一个ExpressionParser类,它就是负责表达式的词法解析的,该类里有一个Token的机构,这就是上面所说的符号,TokenId表示符号的类型
<"Samples"1033"CSharpSamples"LinqSamples"DynamicQuery>)
对于Lambda表达式,有两种编译方式,常规的:
Func<string,bool> IsTrue = username => username == “yuyi”;
这个时候编译器会将其编译为匿名方法,关于匿名方法的相关介绍可以参看我这篇文章。
编译成表达式树:
Expression<Func<string,bool>> IsTrue = username => username == “yuyi”;
这么细微的差别,C#这次却不真的编译这条语句,而是将其进行词法、语法分析,得到的是一个数据结构。
我们注意到表达式树还有一个Compile实例方法,它可以将表达式编译成匿名方法,也就是这个数据结构可以向匿名方法转变。
想想Linq to SQL,实际上它不就是C#作为源语言,SQL作为目标语言的一个编译过程么。形成表达式树(抽象语法树)后,就是代码生成了,只不过这个代码生成有的时候是生成代码,比如Linq to SQL,生成的是SQL语句,有的时候是生成的方法调用,比如Linq to XML,生成的是对XML文档的操作。
为什么一样的语句,有的时候是操作内存中的对象集合,有的时候是操作远程数据库?请查看我这篇文章。
关于表达式树更多细节内容,你可以查看TerryLee老大的这篇图文并茂的文章
已经有很多很多人聊过这个话题,今天我在这里重复也不会探讨出什么新东西,只是把自己的理解描述出来,更是为了整个系列文章的完整性。
当你听说Linq给你的承诺时,你怎么想的?Wow,我们可以以统一的方式操作各种各样的数据了。这就是我当时的想法。虽然人们在现实中总是喜欢差异,认为差异才能产生美,如果一切的一切都是一样的,这个世界将无比的单调,可是作为程序员的我们却对标准趋之若鹜,对差异嫉恶如仇。看同桌的你是不是正在为了Oracle和Sql Server两种数据库编写两套数据访问的类?
表达式树概念
Linq的承诺貌似Java那个梦想一样:Write Once,Run Anywhere。Java是怎么做到的?Sun等公司为我们在各种平台架构上实现了各自的虚拟机,Java的编译分为两个阶段,第一阶段将Java代码编译为字节码,在这个阶段不管在什么平台上,只要Java源代码一样生成的字节码是一致的,第二个阶段,也就是运行阶段,虚拟机会根据平台的不同生成不同的代码。就是通过将编译器分为前端和后端来实现这个梦想。
实际上LINQ也是这么做的,对各种数据的操作无非“增删查改”,但是具体做的时候关系数据库需要使用SQL来操作,而XML需要XPath来操作。我们如何将“增删查改”的语法做到一致,让我们用起来好像操作的数据只有一种?
答案是使用表达式树
表达式树仅仅是将表达式(这里特指Lambda表达式)用树状的数据结构来表示。相当于Java的字节码,至于如何去解析这个树的结构那就看你自己了,如何去解释这个表达式树。
看下面这个Lambda表达式:
username => username == “yuyi”
我们主要看表达式的主体:username == “yuyi”,如果我们是要对数据库进行操作,这将翻译为字段username中所有值为”yuyi”的行,如果操作的是XML那也许是查询名称为username,值为”yuyi”的attribute。表达式树承载的只是这样一个结构:
如何解释它这是你的事。
在C#中Expression<Function<string,bool>> IsTrue = username=>username==”yuyi”,就表示一个表达式树,这个语句在编译后就组成为一个树状的结构。
在编译原理中,我们知道编译器的前两个阶段主要做的是:词法分析、语法分析。在词法分析中编译器会从代码文件中读入一个个的字符,然后识别出其中的关键字、标识符、运算符、常量、字符串等。
比如上面的表达式就会识别出:
Username->标识符
==->运算符
“yuyi”->字符串
这些东西就叫做符号
然后以这些符号作为输入进行语法分析,语法分析会将这些符号组合成一个树,这个树我们称之为抽象语法树(AST),表达式树也是一种简单的AST。
(在VS2008带的例子中有个DynamicQuery的例子,这里有一个Dynamic.cs文件,这里就自己做表达式的解析,只不过解析的是用字符串形式的表达式,通过这个代码你可以看看表达式的解析过程,这里有一个ExpressionParser类,它就是负责表达式的词法解析的,该类里有一个Token的机构,这就是上面所说的符号,TokenId表示符号的类型
<"Samples"1033"CSharpSamples"LinqSamples"DynamicQuery>)
对于Lambda表达式,有两种编译方式,常规的:
Func<string,bool> IsTrue = username => username == “yuyi”;
这个时候编译器会将其编译为匿名方法,关于匿名方法的相关介绍可以参看我这篇文章。
编译成表达式树:
Expression<Func<string,bool>> IsTrue = username => username == “yuyi”;
这么细微的差别,C#这次却不真的编译这条语句,而是将其进行词法、语法分析,得到的是一个数据结构。
我们注意到表达式树还有一个Compile实例方法,它可以将表达式编译成匿名方法,也就是这个数据结构可以向匿名方法转变。
想想Linq to SQL,实际上它不就是C#作为源语言,SQL作为目标语言的一个编译过程么。形成表达式树(抽象语法树)后,就是代码生成了,只不过这个代码生成有的时候是生成代码,比如Linq to SQL,生成的是SQL语句,有的时候是生成的方法调用,比如Linq to XML,生成的是对XML文档的操作。
为什么一样的语句,有的时候是操作内存中的对象集合,有的时候是操作远程数据库?请查看我这篇文章。
关于表达式树更多细节内容,你可以查看TerryLee老大的这篇图文并茂的文章
=================================================
本人之前从未接触过表达式树的概念,所以特意从网上找到两篇这方面的资料学习了下。本文为阅读笔记性质博客!
表达式树是.NET 3.5之后引入的,它是一个强大灵活的工具(比如用在LINQ中构造动态查询)。
先来看看Expression类的API接口:
using System.Collections.ObjectModel; namespace System.Linq.Expressions { // Summary: // Represents a strongly typed lambda expression as a data structure in the // form of an expression tree. This class cannot be inherited. // // Type parameters: // TDelegate: // The type of the delegate that the System.Linq.Expressions.Expression<tdelegate> // represents. public sealed class Expression<tdelegate> : LambdaExpression { // Summary: // Compiles the lambda expression described by the expression tree into executable // code. // // Returns: // A delegate of type TDelegate that represents the lambda expression described // by the System.Linq.Expressions.Expression<tdelegate>. public TDelegate Compile(); } }
表达式树的语法如下:
Expression<Func<type,returnType>> = (param) => lamdaexpresion;
我们先来看一个简单例子:
Expression<Func<int, int, int>> expr = (x, y) => x+y;
这就是一个表达式树了。使用Expression Tree Visualizer工具(直接调试模式下看也可以,只不过没这个直观)在调试模式下查看这个表达式树(就是一个对象),如下:
可以看到表达式树主要由下面四部分组成:
1、Body 主体部分 2、Parameters 参数部分 3、NodeType 节点类型 4、Lambda表达式类型
对于前面举的例子,主体部分即x+y,参数部分即(x,y)。Lambda表达式类型是Func<Int32, Int32, Int32>。注意主体部分可以是表达式,但是不能包含语句,如下这样:
Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };
会报编译错误“Lambada expression with state body cannot be converted to expression tree”:即带有语句的Lambda表达式不能转换成表达式树。
用前面的方法虽然可以创建表达式树,但是不够灵活,如果要灵活构建表达式树,可以像下面这样:
ParameterExpression exp1 = Expression.Parameter(typeof(int), "a"); ParameterExpression exp2 = Expression.Parameter(typeof(int), "b"); BinaryExpression exp = Expression.Multiply(exp1,exp2); var lamExp = Expression.Lambda<Func<int, int, int>>(exp, new ParameterExpression[] { exp1, exp2 });
exp1、exp2即表达式树的参数,exp是表达式树的主体。如果我利用Reflector反编译Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };得到下面的C#代码:
ParameterExpression CS$0$0000; ParameterExpression CS$0$0001; Expression<Func<int, int, int>> expr = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(CS$0$0000 = Expression.Parameter(typeof(int), "x"), CS$0$0001 = Expression.Parameter(typeof(int), "y")), new ParameterExpression[] { CS$0$0000, CS$0$0001 });
可以看到它基本和上面的手动构建代码一致。再来看一个简单的例子:
Expression<Func<Customer, bool>> filter = cust => Equal(Property(cust,"Region"),"North");
可以用下面的代码手动构建效果等同于上面的表达式树:
// declare a parameter of type Customer named cust ParameterExpression custParam = Expression.Parameter( typeof(Customer), "custParam"); // compare (equality) the Region property of the // parameter against the string constant "North" BinaryExpression body = Expression.Equal( Expression.Property(custParam, "Region"), Expression.Constant("North", typeof(string))); // formalise this as a lambda Expression<Func<Customer, bool>> filter = Expression.Lambda<Func<Customer, bool>>(body, cust);
然后我们可以通过表达式树的Compile方法将表达式树编译成Lambda表达式,如下:
Func<Customer, bool> filterFunc = filter.Compile();
但是Compile调用过程涉及动态代码生成,所以出于性能考虑最好只调用一次,然后缓存起来。或者像下面这样在静态构造块中使用(也只会调用一次):
public static class Operator<T> { private static readonly Func<T, T, T> add; public static T Add(T x, T y) { return add(x, y); } static Operator() { var x = Expression.Parameter(typeof(T), "x"); var y = Expression.Parameter(typeof(T), "y"); var body = Expression.Add(x, y); add = Expression.Lambda<Func<T, T, T>>( body, x, y).Compile(); } }
Expression类包含下面几类静态方法(.NET 3.5中):
Arithmetic: Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power, Subtract, SubtractChecked, UnaryPlus Creation: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit Bitwise: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>) Logical: AndAlso (&&), Condition (? :), Equal, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, NotEqual, OrElse (||), TypeIs Member Access: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField Other: Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Parameter, TypeAs, Quote
下面我们类似前面重载一个浅拷贝的例子(比使用反射开销小):
using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var p = new Person() { Name = "jxq", Age = 23 }; var shallowCopy = Operator<Person>.ShallowCopy(p); shallowCopy.Name = "feichexia"; Console.WriteLine(shallowCopy.Name); Console.WriteLine(p.Name); Console.ReadKey(); } public class Person { public string Name { get; set; } public int Age { get; set; } } public static class Operator<T> { private static readonly Func<T, T> ShallowClone; public static T ShallowCopy(T sourcObj) { return ShallowClone.Invoke(sourcObj); } static Operator() { var origParam = Expression.Parameter(typeof(T), "orig"); // for each read/write property on T, create a new binding // (for the object initializer) that copies the original‘s value into the new object var setProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) where prop.CanRead && prop.CanWrite select (MemberBinding)Expression.Bind(prop, Expression.Property(origParam, prop)); var body = Expression.MemberInit( // object initializer Expression.New(typeof(T)), // ctor setProps // property assignments ); ShallowClone = Expression.Lambda<Func<T, T>>(body, origParam).Compile(); } } } }
继续看Expression.AndAlso的使用,它可以用来替代类似下面这种多条件与的情况:
Func<Person, Person, bool> personEqual = (person1, person2) => person1.Name == person2.Name && person1.Age == person2.Age; if(personEqual(p1, p2)) { Console.WriteLine("两个对象所有属性值都相等!"); }
代码如下:
using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var p1 = new Person() { Name = "jxq", Age = 23 }; var p2 = new Person() { Name = "jxq", Age = 23 }; if (Operator<Person>.ObjectPropertyEqual(p1, p2)) { Console.WriteLine("两个对象所有属性值都相等!"); } Console.ReadKey(); } public class Person { public string Name { get; set; } public int Age { get; set; } } public static class Operator<T> { private static readonly Func<T, T, bool> PropsEqual; public static bool ObjectPropertyEqual(T obj1, T obj2) { return PropsEqual.Invoke(obj1, obj2); } static Operator() { var x = Expression.Parameter(typeof(T), "x"); var y = Expression.Parameter(typeof(T), "y"); // 获取类型T上的可读Property var readableProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) where prop.CanRead select prop; Expression combination = null; foreach (var readableProp in readableProps) { var thisPropEqual = Expression.Equal(Expression.Property(x, readableProp), Expression.Property(y, readableProp)); if(combination == null) { combination = thisPropEqual; } else { combination = Expression.AndAlso(combination, thisPropEqual); } } if(combination == null) // 如果没有需要比较的东西,直接返回false { PropsEqual = (p1, p2) => false; } else { PropsEqual = Expression.Lambda<Func<T, T, bool>>(combination, x, y).Compile(); } } } } }
在.NET 4.0中扩展了一些Expression的静态方法,使得编写动态代码更容易:
Mutation: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign, RightShiftAssign, SubtractAssign, SubtractAssignChecked Arithmetic: Decrement, Default, Increment, OnesComplement Member Access: ArrayAccess, Dynamic Logical: ReferenceEqual, ReferenceNotEqual, TypeEqual Flow: Block, Break, Continue, Empty, Goto, IfThen, IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, Variable Exceptions: Catch, Rethrow, Throw Debug: ClearDebugInfo, DebugInfo
下面是一个利用表达式树编写动态代码的例子(循环打印0到9):
using System; using System.Linq.Expressions; namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var exitFor = Expression.Label("exitFor"); // jump label var x = Expression.Variable(typeof(int), "x"); var body = Expression.Block( new[] { x }, // declare scope variables Expression.Assign(x, Expression.Constant(0, typeof(int))), // init Expression.Loop( Expression.IfThenElse( Expression.GreaterThanOrEqual( // test for exit x, Expression.Constant(10, typeof(int)) ), Expression.Break(exitFor), // perform exit Expression.Block( // perform code Expression.Call( typeof(Console), "WriteLine", null, x), Expression.PostIncrementAssign(x) ) ), exitFor ) // Loop ends ); var runtimeLoop = Expression.Lambda<Action>(body).Compile(); runtimeLoop(); Console.Read(); } } }
另外WhereIn扩展实现如下,如果前面的例子都熟悉了的话,这个自然也很容易看懂了:
/// <summary> /// 使之支持Sql in语法 /// </summary> /// <typeparam name = "T"></typeparam> /// <typeparam name = "TValue"></typeparam> /// <param name = "query"></param> /// <param name = "obj"></param> /// <param name = "values"></param> /// <returns></returns> public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query, Expression<Func<T, TValue>> obj, IEnumerable<TValue> values) { return query.Where(BuildContainsExpression(obj, values)); } private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>( Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values) { if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); } if (null == values) { throw new ArgumentNullException("values"); } var p = valueSelector.Parameters.Single(); if (!values.Any()) return e => false; var equals = values.Select(value => (Expression) Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof (TValue)))); var body = equals.Aggregate(Expression.Or); return Expression.Lambda<Func<TElement, bool>>(body, p); }
调用方式如下:
db.Users.WhereIIn(u => u.Id, new int[] { 1, 2, 3 });
关于使用表达式树构建LINQ动态查询,请参考Dynamic Linq Queries with Expression Trees
标签:
原文地址:http://www.cnblogs.com/Huaran1chendu/p/5049913.html