到目前为止,我们只是用迭代器,从一段数据(如果有的话)生成序列。然而,迭代器通常用来以某种方式,进行序列的转换。作为一个简单的例子,这里有一个方法,把数字序列转换成平方序列:
IEnumerable<int>Squares(IEnumerable<int> numbers) {
foreach(int i in numbers)
yield return i * i;
}
我们使用的是熟悉的 foreach 结构,但是要记住,包含 yield return 语句的foreach,有了不同的含义,它不会提前进行循环,而是在需要时才计算。foreach 语句,为每次循环迭代生成元素的代码,对应于从输入序列中取出(pulling)一个元素,把零或多个元素推到(pushing)输出序列中(在前面的例子中,我们始终只生成一个元素)。如果想要实现 LINQ to Objects 中的泛型 Where 和 Select 方法,可以使用完全相同的方法。
有一个更复杂的例子,我们想实现 Zip 方法,与 F# 中的 Seq.zip 函数的功能相同;给它两个序列,将返回一个序列,包含的元素是把给定的序列连接成元组。在 .NET 4.0 的库中有这个方法,但我们要讨论一下,因为它能展示了一个有趣的问题;不能使用 foreach 同时从两个源序列取元素。从清单 12.8 中可以看到,唯一的选择是直接使用 IEnumerable<T> 和 IEnumerator<T> 接口。
清单 12.8 实现 Zip 方法 (C#)
public static IEnumerable<Tuple<T1,T2>> Zip<T1, T2>
(IEnumerable<T1> first, IEnumerable<T2> second) {
using(var firstEn = first.GetEnumerator()) | [1]
using(var secondEn = second.GetEnumerator()) { |
while (firstEn.MoveNext() && secondEn.MoveNext()) { [2]
yield return Tuple.Create(firstEn.Current, secondEn.Current); [3]
}
}
}
从方法的签名可以看出,参数为两个序列;方法是泛型的,每个输入序列有单独的类型参数。我们使用的是泛型 C# 元组,因此,返回序列包含 Tuple<T1, T2> 类型的元素。在具体实现中,我们首先获得每个序列能够进行遍历元素的枚举器[1],然后,在每个枚举上重复调用 MoveNext 方法,从两个序列中获得下一个元素[2]。如果有一个序列已经结束,我们就产生包含每个枚举器当前元素的元组[3]。
这个示例表明,有时修,处理方法需要显式使用 IEnumerator <T> 接口。foreach 循环为我们提供了从一个源中逐个提取出元素的方法,但是,如果需要从多个源中交替地提取元素,就有问题了。如果我们要在 F# 中实现 Seq.zip,就只能使用相同的方法。既可以在序列表达式中使用 while 循环,也可以使用递归的序列表达式。我们需要的大部分处理函数,在 .NET 和 F# 库中已经有了,所以,我们既可以显式使用,也可以通过使用 C# 的查询表达式语法使用。