之前写过一些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例子
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>)
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调用一下代码:
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>,所以可以写成上面的代码形式。根据以上分析,在获取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。接下来将会分析这些。
待续。。。
版权所有归"布衣软件工作者".未经容许不得转载.