当前位置:主页   - 电脑 - 程序设计 - C#
Linq系列:基础与本质(Part I)
来源:网络   作者:   更新时间:2012-08-05
收藏此页】    【字号    】    【打印】    【关闭

  之前写过一些C#3.x新的特性。请参考:C#3.x特性,我们知道这些新的特性基本都是为实现LINQ服务的,在平常的编程中也可以有选择的合理应用,也会有效提高编码效率,实现可读性比较强的简洁代码。在认识这些特性的基础上,理解认识LINQ将变得简单了。

  1 LINQ简介:

  LINQ 查询表达式(query expressions )可以使用统一的方式对实现IEnumberable<T>接口的对象、关系数据库、数据集(Datasets)以及XML文档进行访问。

  严格的说,LINQ是用来描述一系列数据访问技术的术语。LINQ to Objects 适用于实现IEnumberable<T>接口的对象,LINQ to SQL 适用于关系数据库, LINQ to DataSet is则是 LINQ to SQL的一个子集, LINQ to XML 适用于XML 文档。LINQ 查询表达式 是强类型的(strongly typed),因此编译器会确保其语法正确性。LINQ是一个可扩展技术,第三方可以使用扩展函数来定义新的查询操作符。

  2LINQ核心程序集(Assembly)

  至少需要引用System.Linq 命名空间,在System.Core.dll中定义,Visual stuodio 2008默认会自动添加引用。

System.Core.dllDefines the types that represent the core LINQ API. This is the one assembly you must have access to.
System.Data.Linq.dllProvides functionality for using LINQ with relational databases (LINQ to SQL).
System.Data.DataSetExtensions.dllDefines a handful of types to integrate ADO.NET types into the LINQ programming paradigm (LINQ to DataSet).
System.Xml.Linq.dllProvides functionality for using LINQ with XML document data (LINQ to XML).

  3 LINQ例子

  Code

1 static void QueryOverInts()
2     {
3       int[] numbers = { 10, 20, 28, 40, 1, 3, 5, 8 };
4       IEnumerable<int> subset = from i in numbers
5                    where i < 10
6                    select i;
7       IEnumerable<int> subset1 = numbers.Where( j => j < 10 ).
8                     OrderBy( j => j );
9       foreach( int i in subset )
10         Console.WriteLine( "Item: {0}", i );
11       foreach( int i in subset1 )
12         Console.WriteLine( "Item: {0}", i );
13      
14

  我们看上面的代码定义了两个集合subset和subset1。分别通过查询表达式和Lambda表达式生成。那么LINQ内部到底是怎么实现的?这两种方式到底有什么不同呢。我们先来看看这段代码长生的IL。

  Code

1 IL_0014: ldsfld   class [System.Core]System.Func`2<int32,bool> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
2  IL_0019: brtrue.s  IL_002e
3  IL_001b: ldnull
4  IL_001c: ldftn   bool LINQProject.Program::'<QueryOverInts>b__0'(int32)
5  IL_0022: newobj   instance void class [System.Core]System.Func`2<int32,bool>::.ctor(object,
6                                             native int)
7  IL_0027: stsfld   class [System.Core]System.Func`2<int32,bool> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
8  IL_002c: br.s    IL_002e
9  IL_002e: ldsfld   class [System.Core]System.Func`2<int32,bool> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
10  IL_0033: call    class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,
11                           class [System.Core]System.Func`2<!!0,bool>)
12  IL_0038: stloc.1
13  IL_0039: ldloc.0
14  IL_003a: ldsfld   class [System.Core]System.Func`2<int32,bool> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate4'
15  IL_003f: brtrue.s  IL_0054
16  IL_0041: ldnull
17  IL_0042: ldftn   bool LINQProject.Program::'<QueryOverInts>b__1'(int32)
18  IL_0048: newobj   instance void class [System.Core]System.Func`2<int32,bool>::.ctor(object,
19                                             native int)
20  IL_004d: stsfld   class [System.Core]System.Func`2<int32,bool> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate4'
21  IL_0052: br.s    IL_0054
22  IL_0054: ldsfld   class [System.Core]System.Func`2<int32,bool> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate4'
23  IL_0059: call    class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,
24                         class [System.Core]System.Func`2<!!0,bool>)
25  IL_005e: ldsfld   class [System.Core]System.Func`2<int32,int32> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate5'
26  IL_0063: brtrue.s  IL_0078
27  IL_0065: ldnull
28  IL_0066: ldftn   int32 LINQProject.Program::'<QueryOverInts>b__2'(int32)
29  IL_006c: newobj   instance void class [System.Core]System.Func`2<int32,int32>::.ctor(object,                                           native int)
30  IL_0071: stsfld   class [System.Core]System.Func`2<int32,int32> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate5'
31  IL_0076: br.s    IL_0078
32  IL_0078: ldsfld   class [System.Core]System.Func`2<int32,int32> LINQProject.Program::'CS$<>9__CachedAnonymousMethodDelegate5'
33  IL_007d: call    class [System.Core]System.Linq.IOrderedEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::OrderBy<int32,int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,class [System.Core]System.Func`2<!!0,!!1>)

  我们看看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调用一下代码:

  Code

1 static void ReflectOverQueryResults( object resultSet )
2     {
3       Console.WriteLine( "resultSet is of type: {0}", resultSet.GetType().Name );
4       Console.WriteLine( "resultSet location: {0}", resultSet.GetType().Assembly );
5

  我们可以得出subset的类型是<WhereIterator>d__0`1,subset1的类型是OrderedEnumerable`2,可见LINQ表达式的形式不同,其结果类型就不同。但是由于以上Where和OrderBy都实现了IEnumerable<T>,所以可以写成上面的代码形式。根据以上分o,在获取LINQ的返回结果时,最好使用 var 关键字。如:

var subset = from i in numbers where i < 10 select i;

  4 LINQ特性

  我们对上面的实例代码加上下面几行:

  Code

numbers[ 0 ] = 5;
foreach( int i in subset )
 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。接下来将会分析这些。

  待续。。。

其它资源
来源声明

版权与免责声明
1、本站所发布的文章仅供技术交流参考,本站不主张将其做为决策的依据,浏览者可自愿选择采信与否,本站不对因采信这些信息所产生的任何问题负责。
2、本站部分文章来源于网络,其版权为原权利人所有。由于来源之故,有的文章未能获得作者姓名,署“未知”或“佚名”。对于这些文章,有知悉作者姓名的请告知本站,以便及时署名。如果作者要求删除,我们将予以删除。除此之外本站不再承担其它责任。
3、本站部分文章来源于本站原创,本站拥有所有权利。
4、如对本站发布的信息有异议,请联系我们,经本站确认后,将在三个工作日内做出修改或删除处理。
请参阅权责声明