背景
由於枚舉器存在遍歷二叉樹不方便的問題。才有了迭代器。在了解了迭代器之前必須了解枚舉器。
迭代器就是帶了狀態機的枚舉器。
基本介紹
1)迭代器模式( lterator Pattern)是常用的設計模式,屬於行為型模式
2)如果我們的集合元素是用不同的方式實現的,有數組,還有java的集合類,
或者還有其他方式,當客戶端要遍歷這些集合元素的時候就要使用多種遍歷方式,而且還會暴露元素的內部結構,可以考慮使用迭代器模式解決。
3)迭代器模式,提供一種遍歷集合元素的統一接口,用一致的方法遍歷集合元素,
不需要知道集合對象的底層表示,即:不暴露其內部的結構。
迭代器簡介
至此,你已了解 foreach 的內部實現代碼,是時候了解如何使用迭代器創建 IEnumerator<T>、IEnumerable<T> 和自定義集合對應的非泛型接口的自定義實現代碼了。迭代器提供明確的語法,用於指定如何迭代集合類中的數據,尤其是使用 foreach 循環。這樣一來,集合的最終用戶就可以瀏覽其內部結構,而無需知道相應結構。
枚舉模式存在的問題是,手動實現起來不方便,因為必須始終指示描述集合中的當前位置所需的全部狀態。對於列表集合類型類,指示這種內部狀態可能比較簡單;當前位置的索引就足夠了。相比之下,對於需要遞歸遍歷的數據結構(如二叉樹),指示狀態可能就會變得相當復雜。為了減少實現此模式所帶來的挑戰,C# 2.0 新增了 yield 上下文關鍵字,這樣類就可以更輕松地決定 foreach 循環如何迭代其內容。
語法信息
1、迭代器可用作一種方法,或一個 get 訪問器。 不能在事件、實例構造函數、靜態構造函數或靜態終結器中使用迭代器。
2、必須存在從 yield return 語句中的表達式類型到迭代器返回的 IEnumerable<T> 類型參數的隱式轉換。
3、在 C# 中,迭代器方法不能有任何 in、ref 或 out 參數。
4、在 C# 中,yield 不是保留字,只有在 return 或 break 關鍵字之前使用時才有特殊含義。
迭代器塊
迭代器塊是有一個或多個yield語句的代碼塊。下面3種類型的代碼塊中的任意一種都可以是迭代器塊:
- 方法主體
- 訪問器主體
- 運算符主體
迭代器塊與其他代碼塊不同。其他塊包含的語句被當做命令式。即先執行代碼塊中的第一個語句,然后執行后面的語句,最后控制離開塊。
另一方面,迭代器塊不是需要在同一時間執行的一串命令式命令,而是描述了希望編譯器為我們創建的枚舉器類的行為。迭代器塊中的代碼描述了如何枚舉元素。
迭代器塊由兩個特殊語句:
- yield return語句指定了序列中返回的下一項
- yield break語句指定在序列中沒有的其他項
編譯器得到有關枚舉項的描述后,使用它來構建包含所有需要的方法和屬性實現的枚舉器類。結果類被嵌套包含在迭代器聲明的類中。
如下圖所示,根據迭代器塊的返回類型,你可以讓迭代器產生枚舉器或可枚舉類型。

使用迭代器來創建枚舉器
class MyClass { //該類目前已實現GetEnumerator()使類本身可枚舉 public IEnumerator<string> GetEnumerator()//迭代器 { yield return "black"; yield return "gray"; yield return "white"; } } class Program { static void Main() { var mc = new MyClass(); foreach (string shade in mc)////該類目前已實現GetEnumerator()使類本身可枚舉 所以 寫mc { Console.WriteLine(shade); } } }
反編譯IL代碼


下圖演示了MyClass的代碼及產生的對象。注意編譯器為我們自動做了多少工作。
- 圖左的迭代器代碼演示了它的返回類型是
IEnumerator<string> - 圖右演示了它有一個嵌套類實現了
IEnumerator<string>
我們發現show類,內部自動生成一個枚舉器,該枚舉帶了狀態機的功能。並且自動繼承了IEnumerator<object>, IEnumerator, IDisposable三個接口。
IEnumerator<T> 和 IEnumerator 接口的類圖

至此我們一目了然,迭代器就是帶了狀態機的枚舉器。
使用迭代器來創建可枚舉類型
之前示例創建的類包含兩部分:產生返回枚舉器方法的迭代器以及返回枚舉器的GetEnumerator方法。
本節例子中,我們用迭代器來創建可枚舉類型,而不是枚舉器。與之前的示例相比,本例有以下不同:
- 若實現GetEnumerator,讓它調用迭代器方法以獲取自動生成的實現IEnumerable的類實例。然后從IEnumerable對象返回由GetEnumerator創建的枚舉器,如圖右
- 若通過不實現GetEnumerator使類本身不可枚舉,仍然可以使用由迭代器返回的可枚舉類,只需要直接調用迭代器方法
本例中,BlackAndWhite迭代器方法返回IEnumerable<string>而不是IEnumerator<string>。因此MyClass首先調用BlackAndWhite方法獲取它的可枚舉類型對象,然后調用對象的GetEnumerator方法來獲取結果,從而實現GetEnumerator方法
class MyClass { //該類目前未實現 GetEnumerator() /* public IEnumerator<string> GetEnumerator() { IEnumerable<string> myEnumerable = BlackAndWhite(); return myEnumerable.GetEnumerator(); }*
public IEnumerable<string> BlackAndWhite()//迭代器 {
yield return "black"; yield return "gray"; yield return "white"; } } class Program { static void Main() { var mc = new MyClass(); //該類目前未實現GetEnumerator()使類本身不可枚舉,仍然可以使用由迭代器返回的可枚舉類,只需要直接調用迭代器方法 foreach (string shade in mc.BlackAndWhite()) { Console.Write(shade); } } }
反編譯IL代碼

迭代器實質
如下是需要了解的有關迭代器的其他重要事項。
- 迭代器需要System.Collections.Generic命名空間
- 在編譯器生成的枚舉器中,Reset方法沒有實現。而它是接口需要的方法,因此調用時總是拋出System.NetSupportedException異常。
在后台,由編譯器生成的枚舉器類是包含4個狀態的狀態機。
- Before 首次調用MoveNext的初始狀態
- Running 調用MoveNext后進入這個狀態。在這個狀態中,枚舉器檢測並設置下一項的為知。在遇到yield return、yield break或在迭代器體結束時,退出狀態
- Suspended 狀態機等待下次調用MoveNext的狀態
- After 沒有更多項可以枚舉
如果狀態機在Before或Suspended狀態時調用MoveNext方法,就轉到了Running狀態。在Running狀態中,它檢測集合的下一項並設置為知。
如果有更多項,狀態機會轉入Suspended狀態,如果沒有更多項,它轉入並保持在After狀態。

練習題:用迭代器現實以下模型

