C# 迭代器 Iterator


背景

由於枚舉器存在遍歷二叉樹不方便的問題。才有了迭代器。在了解了迭代器之前必須了解枚舉器。

迭代器就是帶了狀態機的枚舉器。

基本介紹

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狀態。

 

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

 

源代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM