之前写过一些C#3.x新的特性。请参考:C#3.x特性,我们知道这些新的特性基本都是为实现LINQ服务的,在平常的编程中也可以有选择的合理应用,也会有效提高编码效率,实现可读性比较强的简洁代码。在认识这些特性的基础上,理解认识LINQ将变得简单了。
1 LINQ简介:
LINQ 查询表达式(query expressions )可以使用统一的方式对实现IEnumerable<T>接口的对象、关系数据库、数据集(Datasets)以及XML文档进行访问。
严格的说,LINQ是用来描述一系列数据访问技术的术语。LINQ to Objects 适用于实现IEnumerable<T>接口的对象,LINQ to SQL 适用于关系数据库, LINQ to DataSet is 则是 LINQ to SQL的一个子集, LINQ to XML 适用于XML 文档。LINQ 查询表达式 是强类型的(strongly typed),因此编译器会确保其语法正确性。LINQ是一个可扩展技术,第三方可以使用扩展函数来定义新的查询操作符。
2 LINQ核心程序集(Assembly)
至少需要引用System.Linq 命名空间,在System.Core.dll中定义,Visual stuodio 2008默认会自动添加引用。
|
System.Core.dll |
Defines the types that represent the core LINQ API. This is the one assembly you must have access to. |
|
System.Data.Linq.dll |
Provides functionality for using LINQ with relational databases (LINQ to SQL). |
|
System.Data.DataSetExtensions.dll |
Defines a handful of types to integrate ADO.NET types into the LINQ programming paradigm (LINQ to DataSet). |
|
System.Xml.Linq.dll |
Provides functionality for using LINQ with XML document data (LINQ to XML). |
3 LINQ例子
我们看上面的代码定义了两个集合subset和subset1。分别通过查询表达式和Lambda表达式生成。那么LINQ内部到底是怎么实现的?这两种方式到底有什么不同呢。我们先来看看这段代码长生的IL。
Code
我们看看IL代码的1-11行是subset的长生过程,14-33行是subset1的长生过程。我们先不看25-33行的subset1代码,先关注1-11行,14-24行的代码。我们发现这里的代码除了使用不同的委托外,实际是一样的。先不对其讲解,我们就能确定,通过查询表达式和Lambda表达式生成两个集合subset和subset1对于CLR来说是一样的,没有什么区别。正如我之前在C#3.X系列提到的这些特性是基于编译器的新特性,在CLR层并没有提供新的实质内容,这里LINQ也是一样。编译器会最终实现一个语法映射的过程,将查询表达式翻译,映射成Lambda表达式的形式。
我们了解了其大观上的实现原理,那么我们就仔细看看其具体实现过程。请看IL代码的1-11行。这里是对where语法的实现,我们很容易的看到这里用到的一个委托。这个委托是编译器自动生成的一个静态委托量CS$<>9__CachedAnonymousMethodDelegate3。而这个变量正是来自于System.Func这个新类。这里我们就可以知道LINQ实质还是需要调用委托。除了委托,我们还可以看到编译器会生成一个静态方法:<QueryOverInts>b__0。在这个方法里对你的查询表达式的查询条件进行处理。LINQ实现的关键就是代码10-11行。这里我们看到系统会调用System.Linq.Enumerable.Where<T>方法,结果集也正是通过此方法得到的,传入的第二个参数是由编译器生成的匿名方法,也就是上面说的委托变量。看到这里大家应该对LINQ的工作本质有个大概的了解。至于代码的25-33行,是用System.Linq.Enumerable::OrderBy<int32,int32>方法去实现查询的Orderby语法。实现原理同以上对where的讲解。
我们对以上subset和subset1调用一下代码:
我们可以得出subset的类型是<WhereIterator>d__0`1,subset1的类型是OrderedEnumerable`2,可见LINQ表达式的形式不同,其结果类型就不同。但是由于以上Where和OrderBy都实现了IEnumerable<T>,所以可以写成上面的代码形式。根据以上分析,在获取LINQ的返回结果时,最好使用 var 关键字。如:
var subset = from i in numbers where i < 10 select i;
4 LINQ特性
我们对上面的实例代码加上下面几行:
Console.WriteLine( "Item: {0}", i );
我们可以看到subset结果集前后输出的不同点是一个为10,一个为5,其余元素一样。这里我们就可以看到LINQ 查询表达式只有在迭代访问其内容时,才会被计算并执行。这样可以保证每次访问得到的是最新的数据。
以上是查询的延时执行,那么查询的立即执行怎么实现呢?和简单,我们只需要在查询表达试ToList<T>()就可以了。将查询结果直接放到强类型的结果集中,执行后,这些结果集就和查询表达式没有关系了,可以单独操作。如:IEnumerable<int> subset3 = ( from i in numbers where i < 10 select i).ToList<int>();
这里我们经常会提到System.Linq.Enumerable和 System.Func。接下来将会分析这些。
待续。。。