3.1 LINQ对.NET的扩展
3.2 序列
3.2.1 IEnumerable<T>接口
数组对象均实现了IEnumerable<T>接口,这个IEnumerable<T>这是LINQ的核心!
3.2.3 延迟查询执行
LINQ查询语句非常依赖于延迟求职。用LINQ的话说,我们将其叫做延迟查询执行(deferred query execution),也叫做延迟查询求值(deferredquery evaluation)。这是LINQ中最重要的几个概念之一----若是缺少了这个特性,LINQ执行效率将会大大降低。
延迟查询执行的一个优势在于它节省了资源。其要点在于,知道我们需要迭代访问查询结果时,某个查询所要操作的数据源才会被开始访问。假设某个查询将返回几千个元素。若是在处理第一个元素后就不再需要后续元素了,那么这些后续元素也不会被加载至内存中---因为每个元素都会以顺序的方式依次提供。若是将所有的元素存放在数组或是列表中,即与传统的编程模型一样的话,那么即使程序中可能并不需要,这些元素也均会被加载至内存中。
延迟查询执行的另一个优势在于我们可以将查询的定义和使用分开放在不同的地方。我们甚至可以在需要时多次使用某个已经定义好的查询。
重用查询以得到不同的结果
另一件需要理解的重要特性就是,在第二次迭代访问某个查询时,我们可能会得到不同的结果。下列代码给出了这样的实例
using System;
using System.Linq;
static classQueryReuse
{
static double Square(double n)
{
Console.WriteLine("ComputingSquare("+n+")...");
return Math.Pow(n,2);
}
public static void Main()
{
int[] numbers = {1,2,3};
var query = from n in numbers
select Square(n);
foreach(var n in query)
Console.WriteLine(n);
for(inti=0;i<number.Length;i++)
numbers[i]=numbers[i]+10;
Console.WriteLine("-Collection updated -");
foreach(var n in query)
Console.WriteLine(n);
}
}
在上面的代码中,我们在第一次执行查询之后改变了查询所操作的数据源----给每一项的值都加上了10,然后再开始第二次执行。
两次执行的结果是:
Computing Square(1)…
1
Computing Square(2)…
4
Computing Square(3)…
9
----Collection updated---
Computing Square(11)…
121
Computing Square(12)…
144
Computing Square(13)…
169
第二次迭代再次执行了该查询,同时得到了新的结果。
查询的强制立即执行
可以看到,延迟执行是查询的默认行为。也就是说,只有在请求数据时,查询才开始执行。若是我们期望的是让查询立即执行,那么就需要显示地发出请求。
例如,若想在开始处理查询的结果之前就让上面的这个查询执行完毕。也就是说在打印之前就已经为每个值调用过了Square方法。
下面给出了不使用延迟执行时的输出:
Computing Square(1)…
Computing Square(2)…
Computing Square(3)…
1
4
9
我们是通过额外调用ToList方法来实现上述强制立即执行的。ToList方法是另外一个定义于System.Linq.Enumerable类中的扩展方法:
Foreach(var n inquery.ToList())
Console.WriteLine(n);
只需这样一个简单的修改,代码的表现行为就有了翻天覆地的改变。
ToList方法将依次访问查询的每一个结构,并能创建一个包含查询中每个结果的List<double>对象。现在foreach循环将操作于一个预先确定下来的集合,迭代的过程中也不会再次调用Square方法。
3.3 查询操作符
扩展方法(包括Where、OrderByDescending和Select)的共有特性:
操作于被枚举的集合对象之上;允许管道形式的数据处理;依赖于延迟执行;
3.3.2标准查询操作符
我们可以将查询操作符组合起来,共同在某个可被枚举的数据源上执行一个复杂的操作。一些预定义的查询操作符涵盖了较为广泛的操作,这类查询操作符被叫做标准查询操作符。
按照操作类型分组的标准查询操作符
类型 查询操作符
过滤 OfType、Where
投影 Select、SelectMany
分区 Skip SkipWhile Take TakeWhile
连接 GroupJoin、Join
串联 Concat Concat Concat
排序 OrderBy、OrderByDescending、Reverse、ThenBy、ThenByDescending
分组 GroupBy、ToLookup
集合 Distinct、Except、Intersect、Union
转换 AsEnumerable、AsQueryable、Cast、ToArray、ToDictionary、ToList
等同 SequenceEqual
元素 ElementAt、ElementAtOrDefault、First、FirstOrDefault、Last
LastOrDefault、Single、SingleOrDefault
生成 DefaultIfEmpty、Empty、Range、Repeat
数量 All、Any、Contains
聚集 Aggregate、Average、Count、LongCount、Max、Min、Sum
3.4 查询表达式
查询操作符是指那些用来构成查询的静态方法。不过在编写查询时,我们也可以不适用如下的语法:
var processes =
Process.GetProcesses()
.Where(process=>process.WorkingSet64>20*1024*1024)
.OrderByDescending(process=>process.WorkingSet64)
.Select(process=>new{process.Id,Name=process.ProcessName});
另一种语法则让LINQ查询更像是SQL的查询语句
var processes =
from process inprocess.GetProcesses()
whereprocess.WorkingSet64>20*1024*1024
orderbyprocess.WorkingSet64 descending
select new {process.Id,Name=process.ProcessName };
上面的这种写法就叫做查询表达式,或查询式语法。
3.4.4 限制
在LINQ查询中我们既会直接使用查询操作符,也会使用查询表达式。但即使在使用查询表达式的时候,也有可能要不得不使用一些查询操作符。因为查询表达式语法及关键词只能够支持一部分查询操作符功能。在编写查询表达式时,我们也会在必要的时候恰当地使用某些查询操作符。
C#编译器将把查询表达式翻译成对如下一些查询操作符的调用:Where、Select、SelectMany、Join、GroupJoin、OrderBy、OrderByDescending、ThenBy、ThenByDescending、GroupBy和Cast。
3.5 表达式树
Iqueryable,另一种实现延迟查询执行的方法
使用IEnumerable<T>和迭代器是一种实现延迟查询执行的方法。另一种实现延迟查询执行的方法是表达式树。
这种延迟查询执行的方法正是LINQto SQL中所用到的。对于如下第1章中曾经出现过的代码,直到foreach循环开始遍历contacts变量时,程序才会开始执行SQL语句:
3.6 LINQ的程序集以及命名空间
使用LINQ所必须的一些类和接口定义于随.NET 3.5一起发布的几个程序集中(DLL)。因此若想在程序中使用的话,则必须直到应该引用哪个程序集,并导入(import)哪些命名空间。
其中最主要的一程序集就是System.Core.dll。若想使用LINQ toObjects的相关功能,我们需要导入其包含的System.Linq命名空间。这样我们就能使用定义于System.Linq.Enumerable中的各种标准查询操作符了。需要注意的是,在使用 Visual Studio 2008 创建新项目时,该System.Core.dll程序集已经被默认添加到了项目的引用中。
下图给出了LINQ相关的程序集、命名空间以及其中包含的内容的简要介绍。
3.7 小结
前面我们详细地介绍了LINQ扩展C#和VB.NET框架的方法。总结如下
序列,即LINQ中的迭代器;
延迟查询执行;
查询操作符,即在LINQ上下文中实现查询操作的扩展方法;
查询表达式,即那种类似于SQL的from…where…语法;
表达式树,用来以数据的方式表示查询,进而支持更高的扩展性需求。