1.前言
在使用 LINQ 查询的过程中存在着两种查询方式,一种是立即执行,另一种是延迟执行。下面将主要讲解 LINQ 的特殊支持——延迟执行。
2.延迟执行
延迟执行意味着,他们不是在查询创建的时候执行,而是在使用 foreach 语句遍历的时候执行(换句话说,当 GetEnumerator 的 MoveNext 方法被调用时)。现在考虑下面这种查询的实现:
static void Main(string[] args)
{
List<int> list = new List<int>();
list.Add(1);
IEnumerable<int> result =
from item in list
select item;
list.Add(2);
foreach(var item in result)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
其输出结果为:
1
2
可以发现,在定义了 LINQ 查询后再向 list 中添加新项 “2”,仍然可以在 froeach 语句中输出 ”2“, 这是因为直到f oreach 语句对 result 进行遍历时,LINQ查询才会执行,这便是 LINQ 的延迟执行。
除了下面两种查询运算符,所有其他的运算符都是延迟执行的:
- 返回单个元素或者标量值的查询运算符,如First、Count等。
- 下面这些转换运算符:ToArray、ToList、ToDictionary、ToLookup。
上面两种运算符会被立即执行,因为他们的返回值类型没有提供延迟执行的机制,比如下面的查询会被立即执行。
int matches = list.Where(n => (n % 2) == 0).Count(); // 1
将返回的集合对象分配给一个新变量时,实现的时直接执行,如下面的示例:
static void Main(string[] args)
{
List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
IEnumerable<int> result =
from item in list
select item;
List<int> newlist = list.ToList();
list.Add(6);
foreach (var item in newlist)
{
Console.Write(item + " ");
}
Console.ReadKey();
}
其输出结果如下所示,即使在 ToList 后更改 list 中的项,foreach 语句的执行结果依旧不变:
1 2 3 4 5
3.重复执行
但是,延迟执行带来的一个影响是,当我们重复遍历查询结果时,查询会被重复执行,例如下面的示例:
static void Main(string[] args)
{
var numbers = new List<int>() { 1, 2 };
IEnumerable<int> query = numbers.Select(n => n * 10); // Build query
foreach (int n in query) Console.Write(n + " "); // 10 20
numbers.Clear();
foreach (int n in query) Console.Write(n + " "); // <nothing>
Console.ReadKey();
}
显然之前的查询数据会被覆盖,当我们需要保留之前查询结果时,这显然是不满足要求的,这时候可以使用上文中提到的转换运算符 ToArray、ToList、ToDictionary、ToLookup 方法存储查询结果。
4.变量捕获
延迟执行还有一个不好的副作用。如果查询的lambda表达式引用了程序的局部变量时,查询会在执行时对变量进行捕获。这意味着,如果在查询定义之后改变了该变量的值,那么查询结果也会随之改变。
IEnumerable<char> query = "How are you, friend.";
foreach (char vowel in "aeiou")
query = query.Where(c => c != vowel);
foreach (char c in query) Console.Write(c); //How are yo, friend.
结果 query 中只有"u"被过滤了,可以修改代码如下:
IEnumerable<char> query = "How are you, friend.";
foreach (char vowel in "aeiou")
{
char temp = vowel;
query = query.Where(c => c != temp);
}
foreach (char c in query) Console.Write(c); //Hw r y, frnd.
此时,才能实现对元音字母的过滤。