1. 簡介

  • 迭代器包含了以下四種類型
    • IEnumerable
    • IEnumerable<T>
    • IEnumerator
    • IEnumerator<T>
  • 根據迭代器的返回類型,每個迭代器都有一個 yield type
    • IEnuerable 返回 object
    • IEnuerable<string> 返回 string
  • 基本的語法為:
public static IEnumerable<int> GetFibonacciSeries(int end)
{
    int cnt = 0;
    int prev = 0;
    int curr = 1;
    while (true)
    {
        if (cnt == end) yield break;
        cnt++;
        int tmp = curr + prev;
        yield return curr;
        prev = curr;   
        curr = tmp;
    }
}
public static void Main(string[] args)
{
    
    foreach(int val in GetFibonacciSeries(10))
    {
        Console.WriteLine(val);
    }
}    

2. 延遲執行

  • 雖然同樣的程式可以用 List<T> 的方式重新寫成下面的程式碼,其列印結果也會一樣,但在執行期卻會有很大的差異。
public static List<int> GetFibonacciSeriesList(int end)
{
    int cnt = 0;
    int prev = 0;
    int curr = 1;
    List<int> res = new List<int>();
    while (true)
    {
        if (cnt == end) break;
        cnt++;
        int tmp = curr + prev;
        res.Add(curr);
        prev = curr;
        curr = tmp;
    }
    return res;
}
public static void Main(string[] args)
{
    foreach(int val in GetFibonacciSeriesList(10))
    {
        Console.WriteLine(val);
    }
}    
  • 延遲執行屬於 lambda 演算的一部分,目的是「只在需要獲取計算結果時執行程式」。
public static void Main(string[] args)
{
    // 調用迭代器的方法,從 IEnumerable<T> 取得 IEnumerator<T>
    using (var reader = GetFibonacciSeries(10).GetEnumerator())
    {
        while (reader.MoveNext())
        {
            int val = reader.Current;
            Console.WriteLine(val);
        }
    }
} 
  • 以上兩段程式碼兩段程式碼在執行期的差別為:
    • 前者在執行到 foreach(int val in GetFibonacciSeriesList(10)) 時,會先將 GetFibonacciSeriesList(10) 執行完取得 List<int> 之後才開始 for loop
    • 後者在執行到 MoveNext() 時,程式碼才真正被執行。
  • 執行 yield 語句時,程式碼會停止執行:
    • 拋出 exception
    • 方法執行完畢
    • 遇到 yield break
    • 執行到 yield return,迭代器準備返回值。
  • 那麼延遲執行有什麼好處呢?
    • 不必預先創建一個 List<int>,在 List 本身很大的情況下,可以節省空間。
    • 試想今天的數列是無窮無盡的(如上例的 fibonacci 數列),使用 List<int> 就顯得太冗餘了。
    • 使用迭代器可以達到 Design Pattern 中的關注點分離(Iteration/Process 分離),也就是說,在叫用迭代器時,只需關心有沒有辦法迭代到下一個元素。
    • 程式只需完成需求即可退出,大大的增加效率。
      • 試想今天需要向資料庫拿去十筆最新的資料,若不用延遲執行的話,需要將整筆資料庫的資料用某種資料結構儲存下來,再 for-loop 取出十筆。
      • 若使用延遲執行,取得十筆資料就立即退出,可以大大減少執行時間。

3. finally 的處理

  • 觀察下面的程式碼,並對照輸出結果,可以發現,只有出現一次的 In finally block
    • 只有在執行了 IEnumerator<T>.Dispose() 方法時,才會調用 finally 的區塊。
    • 而每次的 IEnumerator<T>.MoveNext() 都會使程式停止在 yield return
  • 不管是用 using 搭配 IEnumerable.GetEnumerator(),或是使用 foreach(var val in Iterator()) 回傳的結果都是只有出現一次的 In finally block,代表後者隱含了一條 using 的語句。
public static IEnumerable<string> Iterator()
{
    try 
    {
        Console.WriteLine("Before first yield");
        yield return "first";
        Console.WriteLine("Between yields");
        yield return "second";
        Console.WriteLine("After yields");    
    }
    finally
    {
        Console.WriteLine("In finally block");
    }
    
}
public static void Main(string[] args)
{
    using (var reader = Iterator().GetEnumerator())
    {
        while (reader.MoveNext())
        {
            string val = reader.Current;
            Console.WriteLine(val);
        }
    }
}   
// Before first yield
// first
// Between yields
// second
// After yields
// In finally block