【翻译】Pro LINQ Language Integrated Query in C# 2008 -- 第三章 (LINQ TO Objects) 第二节
返回 IEnumerable<T>, Yielding, 后期执行查询
要记住这个重点,许多标准查询操作是一个返回 IEnumerable<T> 的原型,并且我们认为 IEnumerable<T> 是一个序列,如果操作实际上不返该序列时,我们将该操作称为调用。当枚举生成一个序列的项时操作返回一个对象。它是在枚举返回对象过程的中,该查询实际上才执行,并生成一个序列输出项。这个方法的查询称为后执行查询。
当我使用批量生成时,你并不知道,我是指关于C# 2.0 生成关键字,新增到C# 语言中使得书写枚举更加容易。
例如,示例 3-2 代码
示例 3-2. 简单查询
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
IEnumerable<string> items = presidents.Where(p => p.StartsWith("A"));
foreach(string item in items)
Console.WriteLine(item);
当代码行中包含使用 Where 操作的查询时,该行并没有被执行。而是返回一个对象。 它是在枚举返回对象的过程中,该查询实际上才执行。这意味着就有可能,直到输出序列枚举时,查询自身中发生的错误可能没有及时被检测到。
注意:直到输出序列枚举时查询中的错误可能都不会被检测到。
运行结果:
Adams
Arthur
该查询按照预期方式执行。但是,我会有意加入一个错误。下面的代码将尝试检索每个President的名称的第五个字符的索引。当枚举游标到一个长度少于五个字符项时,一个错误将出现。请记住,直到输出序列枚举时,这个错误都将不会发生。请看示例 3-3
示例 3-3. 一个含有异常的简单示例查询
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
IEnumerable<string> items = presidents.Where(s => Char.IsLower(s[4]));
Console.WriteLine("After the query.");
foreach (string item in items)
Console.WriteLine(item);
这段代码编译是正常的,但是运行后的结果如下:
After the query.
Adams
Arthur
Buchanan
Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds
of the array.
…
注意该查询的输出结果。结果输出到第四项时异常发生。只是因为一个查询编译,并看上去运行并没有错误,不假设该查询是bug-free。
另外,因为这些返回 IEnumerable<T> 类型的查询是后期执行的,你可以调用代码来定义该查询。你使用枚举多次结果时,如果数据被改变,每次你枚举结果,你将获取不同结果。示例 3-4 展示了一个后期执行查询,不缓存该查询结果并在下一次枚举结果时改变数据的示例。
示例 2-4. 一个在枚举结果之间改变查询结果的示例。
int[] intArray = new int[] { 1,2,3 };
IEnumerable<int> ints = intArray.Select(i => i);
// Display the results.
foreach(int i in ints)
Console.WriteLine(i);
// Change an element in the source data.
intArray[0] = 5;
Console.WriteLine("---------");
// Display the results again.
foreach(int i in ints)
Console.WriteLine(i);
在我的说明中将得到更多技术,使得发生什么样的情况都将变的透明。当我调用Select操作时,返回一个存储了实现 IEnumerable<int> 类型的 ints 变量对象。到这里,实际上查询还没有执行,但是查询存储在 ints 的对象中。从技术上来讲,既然查询没有执行,实际上一个整形序列也不存在,但是在这种 Select 操作情况下,ints 对象知道如何通过执行查询分配给序列。
当我使用 foreach 语句第一次循环 ints 时,ints 执行查询并获取序列中的一个元素。
下来我在原整数数组中改变一个项目的值。然后我再次调用 foreach 语句循环。这将导致 ints 来再次执行该查询。因为我改变了原数组的项目值,并且因为 ints 是又一次被枚举,所以又一次执行查询,改变的项被返回。
运行结果如下:
1
2
3
---------
5
2
3
注意:即使我只调用查询一次,枚举后的结果都是不相同的。这进一步证实,该查询是后期执行的。如果他不是,这两个枚举的结果将会是相同的。这是有利弊的。如果你不希望操作后期执行,使用一个不返回 IEnumerable<T> 类型的转换操作, 使得这个查询不能后期执行(例如使用 ToArray、ToList、ToDictionary、ToLookup 来创建一个不同的数据结构)。如果数据源改变,缓存结果将不会改变。
示例 3-5 是在示例 3-4 中修改了所有查询返回一个 IEnumerable<int> 为 调用 ToList 操作返回一个 List<int> 。
示例 3-5. 返回一个 List,使得查询立即执行并返回缓存结果。
int[] intArray = new int[] { 1, 2, 3 };
List<int> ints = intArray.Select(i => i).ToList();
// Display the results.
foreach(int i in ints)
Console.WriteLine(i);
// Change an element in the source data.
intArray[0] = 5;
Console.WriteLine("---------");
// Display the results again.
foreach(int i in ints)
Console.WriteLine(i);
运行结果如下:
1
2
3
---------
1
2
3
注意:第二个枚举的结果和第一个枚举的结果相同。这是因为 ToList 方法不是后期执行的,并且实际上查询被立即执行调用。
一个技术讨论 什么是不同的 示例 3-5 和示例3-6 两者的返回值不同,在示例 3-5 中 Select 操作仍然是后期执行,但ToList 操作不是。在查询语句中当 ToList 操作被调用,返回 Select 操作立即执行的枚举对象,即整个查询被执行。
因为最近比较忙可能后面的内容翻译的将会缓慢,请大家谅解。