Linq in action

Linq的书写方式有2种,一种类似SQL风格的写法,称作查询表达式(Query Expression),另一种是使用扩展方法实现,称作方法语法(Fluent Syntax)。如下: 

  
  
  
  
  1. string[] fullNames = { "Anne Williams""John Fred Smith""Sue Green" };  
  2. //查询表达式写法如下:  
  3.  
  4. var query =  
  5.   from fullName in fullNames  
  6.   from name in fullName.Split()  
  7.   orderby fullName, name  
  8.   select name + " came from " + fullName;  
  9.  
  10. //方法语法写法如下  
  11.  
  12. var query = fullNames  
  13.   .SelectMany (fName => fName.Split().Select (name => new { name, fName } ))  
  14.   .OrderBy (x => x.fName)  
  15.   .ThenBy  (x => x.name)  
  16.   .Select  (x => x.name + " came from " + x.fName); 

两者其实是等价的。因为.net并不会真正理解查询表达式,因此编译器会在程序编译时把查询表达式转换为方法语法,即对扩展方法的调用。

-------------------------------------

说到Linq不得不说两个接口,分别是IEnumerable<T>和IQueryable<T>。Linq所能查询的数据必须是集合,并且实现了IEnumerable<T>接口的。.NET中几乎所有的泛型集合都实现了IEnumerable<T>接口。IQueryable<T> 继承于IEnumerable<T> ,因此, IEnumerable<T>可以实现的,IQueryable<T>也可以做。

两者的区别主要在于IEnumerable用于Linq操作内存中的数据,比如Linq to object,而IQueryable用于外部的数据,比如Linq to sql。

当使用Linq to  sql的时候,编译器会通过IQueryable中的provider,把查询表达式翻译成匹配数据源的查询语句,比如SQL语句。而当使用Linq to object的时候,数据源是IEnumerable,根本没有provider。事实上,.NET通过扩展方法来实现查询的,比如Where<T>()方法,本质上是通过迭代器的MoveNext()方法遍历判断每个元素。

----------------------

表达式和语句

简单的理解,表达式只能是单个语句,并且是有输出值的。而语句是有多个句子或者有{}包起来的语句块。

.NET中的用类型Expression<TDelegate>表示表达式类型,是一个泛型,接受一个委托。只含有一个语句拉姆达表达式可以赋值给表达式类型,但是如果是包含语句块的拉姆达表达式则不能赋值给表达式类型

Expression<Func<Object, Object>> identity = o => o;

Expression<Func<Object, Object>> identity = o => { return o; }; //错误

上面的例子可以看到,Expression类型和委托类型很像,可以如下定义委托类型。

Func<Object, Object> identity = o => o;

虽然看上去变量名和值都一样,但是.NET的编译器知道 Expression<TDelegate> 类型和委托类型的不同,它不会把拉姆达表达式直接编译成IL代码,而是生成一些IL,这些IL表示了该拉姆达表达式的表达式目录树。当然,这些表达式树,我们也可以通过手工的方式去编码实现。

表达式其实是一直抽象语法树(AST),在计算机科学中,抽象语法树是一种数据结构,它常用于编译器或者解释器中。之所以要把拉姆达表达式转换成抽象语法树,目的是一些代码可以分析这个表达式并且实现各种不同的操作。表达式在运行时,可以由工具对表达式进行转换,比如在Linq to sql中转换成SQL。

IQueryable类型的扩展方法Where,Select等方法,接受的参数都是表达式类型,

而IEnumerable类型的扩展方法接受的参数是委托类型。例如:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);

因此:

            List<int> lst = new List<int> { 1, 2, 3, 4, 5, 6, 7 };

            var result1 = lst.AsEnumerable().Select(x => { return x; });//正确

            var result2 = lst.AsQueryable().Select(x => { return x; });//错误

            var result3 = lst.AsQueryable().Select(x => x);//正确

Linq是延迟运行的,比如下面的代码

  
  
  
  
  1. static void Main(string[] args)  
  2.         {  
  3.             List<int> lst = new List<int> { 1, 2, 3, 4, 5, 6, 7 };  
  4.             var result = lst.Select(x => doubleit(x));  
  5.         }  
  6.         static int doubleit(int x)  
  7.         {  
  8.             Console.WriteLine("double " + x);  
  9.             return x + x;  
  10.         } 

 运行后发现,doubleit方法根本没有运行。而修改代码,为如下: 

  
  
  
  
  1. List<int> lst = new List<int> { 1, 2, 3, 4, 5, 6, 7 };  
  2. var result = lst.Select(x => doubleit(x));  
  3. foreach (var n in result)  
  4.        Console.WriteLine(n); 

 才会调用doubleit方法,这是因为Linq中的IEnumerable Select扩展方法本质上是一个迭代器,当Select的结果赋给变量的时候,并不立即做查询,只是一个潜在的查询。只有当对迭代器foreach的时候,才会MoveNext的真正的对数据做判断运行。对Where扩展方法也是如此。

 -------------------------------------------

自定义的Linq Provider

下面一个例子演示了自定义实现Linq Provider。

对于实现了IEnumerable接口的类,只需要实现GetEnumerator的方法,既可以了。

对于实现了IQueryable<Folder>, IQueryProvider接口的类,必须实现CreateQuery和Execute。

自定义Linq Provider很复杂,可以参考

http://blogs.msdn.com/mattwar/archive/2007/08/09/linq-building-an-iqueryable-provider-part-i.aspx

学习。还需要学习DLR的一些知识,以及对表达式类的熟练使用。反正我是不怎么会,还有待学习。

参考资料《LINQ IN ACTION》

你可能感兴趣的:(LINQ)