LinQ查询时,我们不会立即得到返回的数据并生成一个序列.定义该查询只是为了得到查询结果所要的执行的步骤,只有开始迭代查询时,查询才会真正的执行.这样,每次执行查询,都会从头开始执行查询的每个步骤,每次遍历/迭代都会得到全新的结果.这就叫延迟求值.相反.很多时候我们要所见即所得.定义什么查询都得到唯一的数据列表.这叫主动求值.
理解延迟查询.举个不太恰当的例子.如C#的Const.我们定义了一个常量.编译时其实都是常量值.是不变的.这就像主动求值.而如果是用变量.则每次变量的内容都不同.这是把这个"变量"(得到查询结果的步骤)插入到代码之中而已.直到真正迭代调用的时候才执行该步骤,从而得到结果.
private static IEnumerable<TResult> Generate<TResult>(int number, Func<TResult> generator)
{
for (int i = 0; i < number; i++)
yield return generator();
}
private static void LazyEvaluation()
{
Console.WriteLine("Start time for Test One:{0}", DateTime.Now);
var sequence = Generate(10, () => DateTime.Now);
Console.WriteLine("Waiting...\tPress Return");
Console.ReadLine();
Console.WriteLine("Iterating...");
foreach (var value in sequence)
Console.WriteLine(value);
Console.WriteLine("Waiting...\tPress Return");
Console.ReadLine();
Console.WriteLine("Iterating...");
foreach (var value in sequence)
Console.WriteLine(value);
}
上面这段代码生成了一个序列.迭代该序列2次.可以看到.2次生成的数据不同..这也说明.序列1与序列2共享的是其构造方式,而不是数据.若要主动求值.只需要
var sequence = Generate(10, () => DateTime.Now).ToList();
随后你可以发现2次生成的数据是一致的.
由于延迟求值的本质,查询表达式可以操作在无限长度的序列之上.
private IEnumerable<int> AllNumbers()
{
int number = 0;
while (number < int.MaxValue)
yield return number++;
}
public void DisplayMaxNum()
{
var answer = from num in AllNumbers()
select num;
var smallNumbers = answer.Take(10);
foreach (var num in smallNumbers)
Console.WriteLine(num);
}
实验后可以知道.这段程序的执行速度非常快.因为它实际上只获取前10个数据.而如果是主动求值.则必须要先得到int.MaxValue的所有值之后再获取前10个元素.性能可想而知.
如果序列的长度可能为无限的话,那么则必须避免使用需要整个序列的方法,采用延迟查询能很完美的解决.如果序列并不是无限的.但有需要过滤的条件.则应该把过滤的条件放在最前执行.
var sortedProductsSlow =
from p in products
orderby p.UnitsInStock descending
where p.xx > 10
select p;
var sortedProductsSlow =
from p in products
where p.xx > 10
orderby p.UnitsInStock descending
select p;
第一种实现将比第二种实现有效率,不过在Linq to object的实现中.所有的产品都会被读取出来再排序.所以两者并没有区别.这主要是Linq to object和 Linq to sql的内部实现不同(将会在后面的lambda中介绍).
利用延迟查询,我们可以创造出更灵活的代码.更简洁的实现.所以.除非特别有必要使用主动求值,否则最好使用延迟求值.