迭代器

當你想回傳一個序列,該序列的呼叫者可以一次只消耗一個元素,而不必先在集合中建立完整結果時,請選擇迭代器。 此方法適用於產生的值、自訂控制流程的篩選,以及呼叫者可能提前停止讀取的序列。 與已完成的集合相比,迭代器改變的是工作發生的時機,而不只是你怎麼寫。

Tip

本文向熟悉方法、迴圈與集合的開發者講解同步迭代器。 完整 yield 語法與限制,請參閱 yield 語句參考文獻。

迭代方法會依序產生一連串的值。 本文著重於會回傳 IEnumerable<T> 的迭代器方法,其回傳值通常由呼叫者使用 foreach 語句來取用。

以 yield return 建立元素

使用 yield return 來取得序列中的下一個元素。 以下迭代器從感測器中選擇可接受的溫度讀數。 在此情況下,-1 代表感測器資料的結束。

int[] readings = [21, 27, 24, -1, 19];

IEnumerable<int> acceptedReadings =
    SelectTemperatures(readings, maximum: 25);

Console.WriteLine("Sequence created.");

foreach (int temperature in acceptedReadings)
{
    Console.WriteLine($"Accepted: {temperature}°C");
}

static IEnumerable<int> SelectTemperatures(
    IEnumerable<int> readings,
    int maximum)
{
    foreach (int reading in readings)
    {
        Console.WriteLine($"Checking {reading}°C");

        if (reading == -1)
        {
            yield break;
        }

        if (reading <= maximum)
        {
            yield return reading;
        }
    }
}

SelectTemperatures 返回 IEnumerable<int>,代表一連串 int 的值。 每個 yield return reading 都會為 foreach 迴圈提供一個值。 呼叫者處理完該值後,迭代器接著處理下一個輸入。

大多數現代 C# 程式碼請使用通用 IEnumerable<T> 形式。 型別參數(例如 int)會告訴呼叫者該序列包含哪個元素型別。

以 yield break 結束序列

在控制權到達方法結束前,請使用 yield break 提前結束迭代器。 在範例中,哨兵值 -1 表示不再有更多讀數可用:

if (reading == -1)
{
    yield break;
}

迭代器也會在執行到本體末端時自然結束。 當條件應立即停止元素產生時使用 yield break 。

取用一個迭代器 foreach

迭代器會暴露出與許多 .NET 集合相同的IEnumerable<T>結構。 呼叫者可以在不知道序列如何產生其元素的情況下使用 foreach :

foreach (int temperature in SelectTemperatures(readings, 25))
{
    Console.WriteLine($"Accepted: {temperature}°C");
}

呼叫者會隨著迴圈推進,請求每個元素。 迭代器在請求間保持位置,並在前一個 yield return 之後繼續。

了解延遲元素產生

呼叫迭代器方法不會立即執行其主體。 當呼叫者開始列舉時,該方法開始產生元素。 這種行為稱為 延遲執行。

範例會在任何 Checking ... 訊息之前列印 Sequence created.。 迭代器只有在迴圈要求其第一個元素時 foreach 才開始檢查讀數。

延遲執行有實際影響:

  • 呼叫者可以在迭代器產生所有元素之前即可開始處理。
  • 迭代器可以透過 yield break 提前停止。
  • 一個從未列舉序列的呼叫端不會執行迭代器本體。
  • 每進行一次新的逐一巡覽,都會再次逐一周遊迭代器。

在方法的目的和文件中明確說明延遲執行。 讀者無法僅從 IEnumerable<T> 推斷執行時序,因為迭代器與集合都能暴露該介面。 避免元素產生產生無關副作用,因為呼叫端可能會在稍後或多次列舉序列。 範例中的診斷訊息說明了該時間點。

選擇迭代器或集合

當值自然地一次一個地出現,或呼叫者不需要立即得到完整結果時,回傳迭代器。 當呼叫者需要穩定快照、索引存取或重複枚舉而無需重新計算元素時,回傳已完成的集合。

兩種方法都能返回相同的元素。 迭代器將元素產生權交由呼叫者控制;建立集合的方法會在該工作返回前完成。

當你想對每個項目執行一個動作而不回傳序列時,可以用普通迴圈。 對於標準篩選或轉換,建議使用例如 Where 或 Select 的 LINQ 運算;若以一連串陳述式表達自訂元素產生邏輯更清楚,則可撰寫迭代器。

迭代器方法可與 LINQ 搭配運作,因為兩者都使用 IEnumerable<T>。 例如,呼叫者可以新增另一個過濾器:

var comfortableReadings = SelectTemperatures(readings, 25)
    .Where(static temperature => temperature >= 18);

迭代器與 LINQ 運算會一直延遲,直到程式碼列舉 comfortableReadings。

關於非同步序列,請參見「產生並消耗非同步串流」。 對於自訂列舉模式及所有 yield 限制,請使用 語言參考。

也請參閱