Linq的书写方式有2种,一种类似SQL风格的写法,称作查询表达式(Query Expression),另一种是使用扩展方法实现,称作方法语法(Fluent Syntax)。如下:
- string[] fullNames = { "Anne Williams", "John Fred Smith", "Sue Green" };
- //查询表达式写法如下:
- var query =
- from fullName in fullNames
- from name in fullName.Split()
- orderby fullName, name
- select name + " came from " + fullName;
- //方法语法写法如下
- var query = fullNames
- .SelectMany (fName => fName.Split().Select (name => new { name, fName } ))
- .OrderBy (x => x.fName)
- .ThenBy (x => x.name)
- .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是延迟运行的,比如下面的代码:
- static void Main(string[] args)
- {
- List<int> lst = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
- var result = lst.Select(x => doubleit(x));
- }
- static int doubleit(int x)
- {
- Console.WriteLine("double " + x);
- return x + x;
- }
运行后发现,doubleit方法根本没有运行。而修改代码,为如下:
- List<int> lst = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
- var result = lst.Select(x => doubleit(x));
- foreach (var n in result)
- 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》