Iterators

呼び出し元が要素を一度に 1 つずつ使用できるシーケンスを返す場合は、コレクションに完全な結果を作成せずに反復子を選択します。 この方法は、生成された値や、カスタム制御フローを使うフィルター、呼び出し元が途中で読むのをやめる可能性のあるシーケンスに適しています。 完成したコレクションと比較すると、反復子は、記述方法だけでなく、作業が行われるタイミングも変えます。

Tip

この記事では、メソッド、ループ、コレクションを知っている開発者向けの同期反復子について説明します。 完全な yield 構文と制限については、 yield ステートメントのリファレンスを参照してください。

イテレーター メソッドは、一度に 1 つずつ値のシーケンスを生成します。 この記事では、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 ループに 1 つの値を提供します。 呼び出し元がその値を処理した後、反復子は次の入力に進みます。

最近の多くの C# コードには、汎用 IEnumerable<T> フォームを使用します。 intなどの型引数は、シーケンスに含まれる要素型を呼び出し元に通知します。

yield breakでシーケンスを終了します

制御がメソッドの末尾に到達する前に、yield break を使用して反復子を終了します。 この例では、sentinel 値 -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 の続きから再開します。

遅延要素の生成を理解する

反復子メソッドを呼び出しても、その本体はすぐには実行されません。 メソッドは、呼び出し元が列挙を開始すると、要素の生成を開始します。 この動作は 、遅延実行と呼ばれます。

この例では、Sequence created.メッセージより前にChecking ...を出力します。 反復子は、foreach ループが最初の要素を要求したときにのみ、読み取りのチェックを開始します。

遅延実行には実用的な効果があります。

  • 呼び出し元は、反復子がすべての要素を生成する前に処理を開始できます。
  • イテレーターは、yield breakを使用して早期に停止できます。
  • シーケンスを列挙しない呼び出し元は、反復子本体を実行しません。
  • 新しい列挙のたびに、イテレーターが再び実行されます。

メソッドの目的とドキュメントで遅延実行を明確にしてください。 反復子とコレクションの両方がそのインターフェイスを公開できるため、リーダーは IEnumerable<T> だけから実行タイミングを推論できません。 呼び出し元は後でまたは複数回シーケンスを列挙する可能性があるため、要素の生成に関連のない副作用を発生させないようにします。 この例の診断メッセージは、そのタイミングを示しています。

イテレーターまたはコレクションを選択

値が自然に 1 つずつ得られる場合、または呼び出し元が完全な結果をすぐに必要としない場合に反復子を返します。 要素を再計算せずに、呼び出し元が安定したスナップショット、インデックス付きアクセス、または繰り返し列挙を必要とする場合は、完成済みのコレクションを返します。

どちらの方法でも、同じ要素を返すことができます。 反復子は、要素の生成を呼び出し元の制御下に置きます。コレクションをビルドするメソッドは、返る前にその作業を完了します。

シーケンスを返さずに各項目に対してアクションを実行する場合は、通常のループを使用します。 標準のフィルターまたは変換の場合は、Where や Select などの LINQ 操作を優先します。カスタム要素生成ロジックが一連のステートメントとして明確な場合は、イテレーターを作成します。

反復子メソッドは、どちらも IEnumerable<T> を使用するため、LINQ と組み合わせることができます。 たとえば、呼び出し元は別のフィルターを追加できます。

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

反復子と LINQ 操作は、コードが comfortableReadings を列挙するまで遅延されたままになります。

非同期シーケンスについては、 非同期ストリームの生成と使用に関するページを参照してください。 カスタム列挙パターンとすべての yield 制限については、言語リファレンスを参照してください。

参照