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