迭代器可用於逐步迭代集合,例如列表和數組。html
迭代器方法或 get
訪問器可對集合執行自定義迭代。 迭代器方法使用 yield return 語句返回元素,每次返回一個。 到達 yield return
語句時,會記住當前在代碼中的位置。 下次調用迭代器函數時,將從該位置從新開始執行。api
經過 foreach 語句或 LINQ 查詢從客戶端代碼中使用迭代器。數組
在如下示例中,foreach
循環的首次迭代致使 SomeNumbers
迭代器方法繼續執行,直至到達第一個 yield return
語句。 此迭代返回的值爲 3,並保留當前在迭代器方法中的位置。 在循環的下次迭代中,迭代器方法的執行將從其暫停的位置繼續,直至到達 yield return
語句後纔會中止。 此迭代返回的值爲 5,並再次保留當前在迭代器方法中的位置。 到達迭代器方法的結尾時,循環便已完成。ide
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; }
迭代器方法或 get
訪問器的返回類型能夠是 IEnumerable、IEnumerable<T>、IEnumerator 或 IEnumerator<T>。函數
可使用 yield break
語句來終止迭代。工具
對於本主題中除簡單迭代器示例之外的全部示例,請爲 System.Collections 和 System.Collections.Generic 命名空間加入 using 指令。ui
yield return
語句。 在 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; } } }
在如下示例中,DaysOfTheWeek
類實現 IEnumerable 接口,此操做須要 GetEnumerator 方法。 編譯器隱式調用 GetEnumerator
方法,此方法返回 IEnumerator。this
GetEnumerator
方法經過使用 yield return
語句每次返回 1 個字符串。spa
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
類。code
引用類實例 (theZoo
) 的 foreach
語句隱式調用 GetEnumerator
方法。 引用 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 }
在如下示例中,Stack<T> 泛型類實現 IEnumerable<T> 泛型接口。 Push 方法將值分配給類型爲 T
的數組。 GetEnumerator 方法經過使用 yield return
語句返回數組值。
除了泛型 GetEnumerator 方法,還必須實現非泛型 GetEnumerator 方法。 這是由於從 IEnumerable 繼承了 IEnumerable<T>。 非泛型實現聽從泛型實現的規則。
本示例使用命名迭代器來支持經過各類方法循環訪問同一數據集合。 這些命名迭代器爲 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 }
迭代器可用做一種方法,或一個 get
訪問器。 不能在事件、實例構造函數、靜態構造函數或靜態終結器中使用迭代器。
必須存在從 yield return
語句中的表達式類型到迭代器返回的 IEnumerable<T> 類型參數的隱式轉換。
在 C# 中,迭代器方法不能有任何 in
、ref
或 out
參數。
在 C# 中,「yield」不是保留字,只有在 return
或 break
關鍵字以前使用時纔有特殊含義。
即便將迭代器編寫成方法,編譯器也會將其轉換爲其實是狀態機的嵌套類。 只要客戶端代碼中的 foreach
循環繼續,此類就會跟蹤迭代器的位置。
若要查看編譯器執行的操做,可以使用 Ildasm.exe 工具查看爲迭代器方法生成的 Microsoft 中間語言代碼。
爲類或結構建立迭代器時,沒必要實現整個 IEnumerator 接口。 編譯器檢測到迭代器時,會自動生成 IEnumerator 或 IEnumerator<T> 接口的 Current
、MoveNext
和 Dispose
方法。
在 foreach
循環(或對 IEnumerator.MoveNext
的直接調用)的每次後續迭代中,下一個迭代器代碼體都會在上一個 yield return
語句以後恢復。 而後繼續下一個 yield return
語句,直至到達迭代器體的結尾,或直至遇到 yield break
語句。
迭代器不支持 IEnumerator.Reset 方法。 若要從頭開始從新迭代,必須獲取新的迭代器。 在迭代器方法返回的迭代器上調用 Reset 會引起 NotSupportedException。
有關其餘信息,請參閱 C# 語言規範。
須要使用複雜代碼填充列表序列時,使用迭代器可保持 foreach
循環的簡單性。 需執行如下操做時,這可能頗有用:
在第一次 foreach
循環迭代以後,修改列表序列。
避免在 foreach
循環的第一次迭代以前徹底加載大型列表。 一個示例是用於加載一批表格行的分頁提取。 另外一個示例關於 EnumerateFiles 方法,該方法在 .NET Framework 中實現迭代器。
在迭代器中封裝生成列表。 使用迭代器方法,可生成該列表,而後在循環中產出每一個結果。