迭代器
迭代器(C# 编程指南)
迭代器是 C# 2.0 中的新功能。迭代器是方法、get 访问器或运算符,它使您能够在类或结构中支持 foreach 迭代,而不必实现整个 IEnumerable 接口。您只需提供一个迭代器,即可遍历类中的数据结构。当编译器检测到迭代器时,它将自动生成 IEnumerable 或 IEnumerable<T> 接口的 Current、MoveNext 和 Dispose 方法。
迭代器概述
-
迭代器是可以返回相同类型的值的有序序列的一段代码。
-
迭代器可用作方法、运算符或 get 访问器的代码体。
-
迭代器代码使用 yield return 语句依次返回每个元素。yield break 将终止迭代。有关更多信息,请参见 yield。
-
可以在类中实现多个迭代器。每个迭代器都必须像任何类成员一样有唯一的名称,并且可以在 foreach 语句中被客户端代码调用,如下所示:foreach(int x in SampleClass.Iterator2){}
-
迭代器的返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>。
yield 关键字用于指定返回的值。到达 yield return 语句时,会保存当前位置。下次调用迭代器时将从此位置重新开始执行。
迭代器对集合类特别有用,它提供一种简单的方法来迭代不常用的数据结构(如二进制树)。
示例
在本示例中,DaysOfTheWeek 类是将一周中的各天作为字符串进行存储的简单集合类。foreach 循环每迭代一次,都返回集合中的下一个字符串。
public class DaysOfTheWeek : System.Collections.IEnumerable { string[] m_Days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" }; public System.Collections.IEnumerator GetEnumerator() { for (int i = 0; i < m_Days.Length; i++) { yield return m_Days[i]; } } } class TestDaysOfTheWeek { static void Main() { // Create an instance of the collection class DaysOfTheWeek week = new DaysOfTheWeek(); // Iterate with foreach foreach (string day in week) { System.Console.Write(day + " "); } } }
输出
Sun Mon Tue Wed Thr Fri Sat
使用迭代器
创建迭代器最常用的方法是对 IEnumerable 接口实现 GetEnumerator 方法,例如:
public System.Collections.IEnumerator GetEnumerator() { for (int i = 0; i < max; i++) { yield return i; } }
GetEnumerator 方法的存在使得类型成为可枚举的类型,并允许使用 foreach 语句。如果上面的方法是 ListClass 的类定义的一部分,则可以对该类使用 foreach,如下所示:
static void Main() { ListClass listClass1 = new ListClass(); foreach (int i in listClass1) { System.Console.WriteLine(i); } }
foreach 语句调用 ListClass.GetEnumerator() 并使用返回的枚举数来循环访问值。有关如何创建返回 IEnumerator 接口的泛型迭代器的示例,请参见如何:为泛型列表创建迭代器块(C# 编程指南)。
还可以使用命名的迭代器以支持通过不同的方式循环访问同一数据集合。例如,您可以提供一个按升序返回元素的迭代器,而提供按降序返回元素的另一个迭代器。迭代器还可以带有参数,以便允许客户端控制全部或部分迭代行为。下面的迭代器使用命名的迭代器 SampleIterator 实现 IEnumerable 接口:
// Implementing the enumerable pattern public System.Collections.IEnumerable SampleIterator(int start, int end) { for (int i = start; i <= end; i++) { yield return i; } }
命名的迭代器的调用方法如下:
ListClass test = new ListClass(); foreach (int n in test.SampleIterator(1, 10)) { System.Console.WriteLine(n); }
可以在同一个迭代器中使用多个 yield 语句,如下面的示例所示:
public System.Collections.IEnumerator GetEnumerator() { yield return "With an iterator, "; yield return "more than one "; yield return "value can be returned"; yield return "."; }
然后可以使用下面的 foreach 语句输出结果:
foreach (string element in new TestClass()) { System.Console.Write(element); }
此示例显示以下文本:
With an iterator, more than one value can be returned.
在 foreach 循环的每次后续迭代(或对 IEnumerator.MoveNext 的直接调用)中,下一个迭代器代码体将从前一个 yield 语句之后开始,并继续下一个语句直至到达迭代器体的结尾或遇到 yield break 语句。
如何:为整数列表创建迭代器块
此示例使用一个整数数组来构建列表 SampleCollection。for 循环会循环访问该集合,并生成每个项的值。然后使用 foreach 循环来显示该集合的项。
示例
// Declare the collection: public class SampleCollection { public int[] items; public SampleCollection() { items = new int[5] { 5, 4, 7, 9, 3 }; } public System.Collections.IEnumerable BuildCollection() { for (int i = 0; i < items.Length; i++) { yield return items[i]; } } } class MainClass { static void Main() { SampleCollection col = new SampleCollection(); // Display the collection items: System.Console.WriteLine("Values in the collection are:"); foreach (int i in col.BuildCollection()) { System.Console.Write(i + " "); } } }
输出
Values in the collection are: 5 4 7 9 3
如何:为泛型列表创建迭代器块
在本示例中,泛型类Stack<T>实现泛型接口 IEnumerator<T>。声明了一个类型 T 的数组,并使用 Push 方法给数组赋值。在 GetEnumerator 方法中,使用 yield return 语句返回数组的值。
还实现非泛型 GetEnumerator,因为 IEnumerable<T> 继承自 IEnumerable。此示例显示了典型的实现,在该实现中,非泛型方法直接将调用转给泛型方法。
示例
using System.Collections; using System.Collections.Generic; namespace GenericIteratorExample { public class Stack<T> : IEnumerable<T> { private T[] values = new T[100]; private int top = 0; public void Push(T t) { values[top++] = t; } public T Pop() { return values[--top]; } // These make Stack<T> implement IEnumerable<T> allowing // a stack to be used in a foreach statement. public IEnumerator<T> GetEnumerator() { for (int i = top; --i >= 0; ) { yield return values[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } // Iterate from top to bottom. public IEnumerable<T> TopToBottom { get { // Since we implement IEnumerable<T> // and the default iteration is top to bottom, // just return the object. return this; } } // Iterate from bottom to top. public IEnumerable<T> BottomToTop { get { for (int i = 0; i < top; i++) { yield return values[i]; } } } //A parameterized iterator that return n items from the top public IEnumerable<T> TopN(int n) { // in this example we return less than N if necessary int j = n >= top ? 0 : top - n; for (int i = top; --i >= j; ) { yield return values[i]; } } } //This code uses a stack and the TopToBottom and BottomToTop properties //to enumerate the elements of the stack. class Test { static void Main() { Stack<int> s = new Stack<int>(); for (int i = 0; i < 10; i++) { s.Push(i); } // Prints: 9 8 7 6 5 4 3 2 1 0 // Foreach legal since s implements IEnumerable<int> foreach (int n in s) { System.Console.Write("{0} ", n); } System.Console.WriteLine(); // Prints: 9 8 7 6 5 4 3 2 1 0 // Foreach legal since s.TopToBottom returns IEnumerable<int> foreach (int n in s.TopToBottom) { System.Console.Write("{0} ", n); } System.Console.WriteLine(); // Prints: 0 1 2 3 4 5 6 7 8 9 // Foreach legal since s.BottomToTop returns IEnumerable<int> foreach (int n in s.BottomToTop) { System.Console.Write("{0} ", n); } System.Console.WriteLine(); // Prints: 9 8 7 6 5 4 3 // Foreach legal since s.TopN returns IEnumerable<int> foreach (int n in s.TopN(7)) { System.Console.Write("{0} ", n); } System.Console.WriteLine(); } } }
输出
9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3