迭代器可用于逐步迭代集合,例如列表和数组。
下次调用迭代器函数时,将从该位置重新开始执行。
foreach 语句或 LINQ 查询从客户端代码中使用迭代器。
到达迭代器方法的结尾时,循环便已完成。
static void Main() { foreach (int number in SomeNumbers()) { Console.Write(number.ToString() + " "); } // 输出: 3 5 8 Console.ReadKey(); } public static System.Collections.IEnumerable SomeNumbers() { yield return 3; yield return 5; yield return 8; }
IEnumerator<T>。
可以使用 yield break 语句来终止迭代。
对于本主题中除简单迭代器示例以外的所有示例,请为 System.Collections 和 System.Collections.Generic 命名空间加入 using 指令。
Main 中,foreach 语句体的每次迭代都会创建一个对迭代器函数的调用,并将继续到下一个 yield return 语句。static void Main() { foreach (int number in EvenSequence(5, 18)) { Console.Write(number.ToString() + " "); } // 输出: 6 8 10 12 14 16 18 Console.ReadKey(); } public static System.Collections.Generic.IEnumerable<int> EvenSequence(int firstNumber, int lastNumber) { // 迭代集合中的偶数. for (int number = firstNumber; number <= lastNumber; number++) { if (number % 2 == 0) { yield return number; } } }
IEnumerator。
GetEnumerator 方法通过使用 yield return 语句每次返回 1 个字符串。
static void Main() { DaysOfTheWeek days = new DaysOfTheWeek(); foreach (string day in days) { Console.Write(day + " "); } // 输出: Sun Mon Tue Wed Thu Fri Sat Console.ReadKey(); } public class DaysOfTheWeek : IEnumerable { private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; public IEnumerator GetEnumerator() { for (int index = 0; index < days.Length; index++) { // 迭代每一天 yield return days[index]; } } }
下例创建了一个包含动物集合的 Zoo 类。
引用 Birds 和 Mammals 属性的 foreach 语句使用 AnimalsForType 命名迭代器方法。
1 static void Main() 2 { 3 Zoo theZoo = new Zoo(); 4 5 theZoo.AddMammal("Whale"); 6 theZoo.AddMammal("Rhinoceros"); 7 theZoo.AddBird("Penguin"); 8 theZoo.AddBird("Warbler"); 9 10 foreach (string name in theZoo) 11 { 12 Console.Write(name + " "); 13 } 14 Console.WriteLine(); 15 // 输出: Whale Rhinoceros Penguin Warbler 16 17 foreach (string name in theZoo.Birds) 18 { 19 Console.Write(name + " "); 20 } 21 Console.WriteLine(); 22 // 输出: Penguin Warbler 23 24 foreach (string name in theZoo.Mammals) 25 { 26 Console.Write(name + " "); 27 } 28 Console.WriteLine(); 29 // 输出: Whale Rhinoceros 30 31 Console.ReadKey(); 32 } 33 34 public class Zoo : IEnumerable 35 { 36 // 私有成员 37 private List<Animal> animals = new List<Animal>(); 38 39 // 公共方法 40 public void AddMammal(string name) 41 { 42 animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal }); 43 } 44 45 public void AddBird(string name) 46 { 47 animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird }); 48 } 49 50 public IEnumerator GetEnumerator() 51 { 52 foreach (Animal theAnimal in animals) 53 { 54 yield return theAnimal.Name; 55 } 56 } 57 58 // 公共成员 59 public IEnumerable Mammals 60 { 61 get { return AnimalsForType(Animal.TypeEnum.Mammal); } 62 } 63 64 public IEnumerable Birds 65 { 66 get { return AnimalsForType(Animal.TypeEnum.Bird); } 67 } 68 69 // 私有方法 70 private IEnumerable AnimalsForType(Animal.TypeEnum type) 71 { 72 foreach (Animal theAnimal in animals) 73 { 74 if (theAnimal.Type == type) 75 { 76 yield return theAnimal.Name; 77 } 78 } 79 } 80 81 // 私有类 82 private class Animal 83 { 84 public enum TypeEnum { Bird, Mammal } 85 86 public string Name { get; set; } 87 public TypeEnum Type { get; set; } 88 } 89 }
GetEnumerator 方法通过使用 yield return 语句返回数组值。
非泛型实现遵从泛型实现的规则。
这些命名迭代器为 TopToBottom 和 BottomToTop 属性,以及 TopN 方法。
BottomToTop 属性在 get 访问器中使用迭代器。
1 static void Main() 2 { 3 Stack<int> theStack = new Stack<int>(); 4 5 // 向堆栈中添加项 6 for (int number = 0; number <= 9; number++) 7 { 8 theStack.Push(number); 9 } 10 11 // 从堆栈中检索项。 12 // 此处允许使用 foreach,因为 foreach 实现了 IEnumerable<int> 13 foreach (int number in theStack) 14 { 15 Console.Write("{0} ", number); 16 } 17 Console.WriteLine(); 18 // 输出: 9 8 7 6 5 4 3 2 1 0 19 20 // 此处允许使用 foreach,因为 theStack.TopToBottom 属性返回了 IEnumerable(Of Integer). 21 foreach (int number in theStack.TopToBottom) 22 { 23 Console.Write("{0} ", number); 24 } 25 Console.WriteLine(); 26 // 输出: 9 8 7 6 5 4 3 2 1 0 27 28 foreach (int number in theStack.BottomToTop) 29 { 30 Console.Write("{0} ", number); 31 } 32 Console.WriteLine(); 33 // 输出: 0 1 2 3 4 5 6 7 8 9 34 35 foreach (int number in theStack.TopN(7)) 36 { 37 Console.Write("{0} ", number); 38 } 39 Console.WriteLine(); 40 // 输出: 9 8 7 6 5 4 3 41 42 Console.ReadKey(); 43 } 44 45 public class Stack<T> : IEnumerable<T> 46 { 47 private T[] values = new T[100]; 48 private int top = 0; 49 50 public void Push(T t) 51 { 52 values[top] = t; 53 top++; 54 } 55 public T Pop() 56 { 57 top--; 58 return values[top]; 59 } 60 61 // 此方法实现了GetEnumerator()方法. 它允许在 foreach 语句中使用类的实例。 63 public IEnumerator<T> GetEnumerator() 64 { 65 for (int index = top - 1; index >= 0; index--) 66 { 67 yield return values[index]; 68 } 69 } 70 71 IEnumerator IEnumerable.GetEnumerator() 72 { 73 return GetEnumerator(); 74 } 75 76 public IEnumerable<T> TopToBottom 77 { 78 get { return this; } 79 } 80 81 public IEnumerable<T> BottomToTop 82 { 83 get 84 { 85 for (int index = 0; index <= top - 1; index++) 86 { 87 yield return values[index]; 88 } 89 } 90 } 91 92 public IEnumerable<T> TopN(int itemsFromTop) 93 { 94 // 如有必要,返回少于 itemsFromTop 95 int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop; 96 97 for (int index = top - 1; index >= startIndex; index--) 98 { 99 yield return values[index]; 100 } 101 } 102 103 }
不能在事件、实例构造函数、静态构造函数或静态终结器中使用迭代器。
必须存在从 yield return 语句中的表达式类型到迭代器返回的 IEnumerable<T> 类型参数的隐式转换。
在 C# 中,迭代器方法不能有任何 in、ref 或 out 参数。
在 C# 中,“yield”不是保留字,只有在 return 或 break 关键字之前使用时才有特殊含义。
只要客户端代码中的 foreach 循环继续,此类就会跟踪迭代器的位置。
若要查看编译器执行的操作,可使用 Ildasm.exe 工具查看为迭代器方法生成的 Microsoft 中间语言代码。
IEnumerator<T> 接口的 Current、MoveNext 和 Dispose 方法。
然后继续下一个 yield return 语句,直至到达迭代器体的结尾,或直至遇到 yield break 语句。
NotSupportedException。
C# 语言规范。
需执行以下操作时,这可能很有用:
-
在第一次
foreach循环迭代之后,修改列表序列。 -
EnumerateFiles 方法,该方法在 .NET Framework 中实现迭代器。
-
使用迭代器方法,可生成该列表,然后在循环中产出每个结果。