yield ステートメント - 次の要素を指定する

反復子yield ステートメントを使うと、シーケンスを反復処理するときにシーケンスの次の値を指定できます。 yield ステートメントには、次の 2 つの形式があります。

  • yield return: 次の例に示すように、反復処理で次の値を指定します。

    foreach (int i in ProduceEvenNumbers(9))
    {
        Console.Write(i);
        Console.Write(" ");
    }
    // Output: 0 2 4 6 8
    
    IEnumerable<int> ProduceEvenNumbers(int upto)
    {
        for (int i = 0; i <= upto; i += 2)
        {
            yield return i;
        }
    }
    
  • yield break: 次の例に示すように、反復処理の終了を明示的に通知します。

    Console.WriteLine(string.Join(" ", TakeWhilePositive(new[] { 2, 3, 4, 5, -1, 3, 4})));
    // Output: 2 3 4 5
    
    Console.WriteLine(string.Join(" ", TakeWhilePositive(new[] { 9, 8, 7 })));
    // Output: 9 8 7
    
    IEnumerable<int> TakeWhilePositive(IEnumerable<int> numbers)
    {
        foreach (int n in numbers)
        {
            if (n > 0)
            {
                yield return n;
            }
            else
            {
                yield break;
            }
        }
    }
    

    反復処理は、コントロールが反復子の末尾に達したときにも終了します。

前の例では、反復子の戻り値の型は IEnumerable<T> です (非ジェネリックの場合は、反復子の戻り値の型として IEnumerable を使用します)。 反復子の戻り値の型として IAsyncEnumerable<T> を使用することもできます。 これにより、反復子が非同期になります。 次の例に示すように、await foreach ステートメントを使用して反復子の結果を反復処理します。

await foreach (int n in GenerateNumbersAsync(5))
{
    Console.Write(n);
    Console.Write(" ");
}
// Output: 0 2 4 6 8

async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
    for (int i = 0; i < count; i++)
    {
        yield return await ProduceNumberAsync(i);
    }
}

async Task<int> ProduceNumberAsync(int seed)
{
    await Task.Delay(1000);
    return 2 * seed;
}

IEnumerator<T> または IEnumerator は、反復子の戻り値の型にすることもできます。 これは、次のシナリオで GetEnumerator メソッドを実装する場合に便利です。

  • IEnumerable<T> または IEnumerable インターフェイスを実装する型を設計します。

  • 次の例に示すように、インスタンスまたは拡張GetEnumerator メソッドを追加し、foreach ステートメントを使用して型のインスタンスに対する反復処理を有効にします。

    public static void Example()
    {
        var point = new Point(1, 2, 3);
        foreach (int coordinate in point)
        {
            Console.Write(coordinate);
            Console.Write(" ");
        }
        // Output: 1 2 3
    }
    
    public readonly record struct Point(int X, int Y, int Z)
    {
        public IEnumerator<int> GetEnumerator()
        {
            yield return X;
            yield return Y;
            yield return Z;
        }
    }
    

以下で yield ステートメントを使うことはできません。

反復子の実行

次の例に示すように、反復子の呼び出しはすぐには実行されません。

var numbers = ProduceEvenNumbers(5);
Console.WriteLine("Caller: about to iterate.");
foreach (int i in numbers)
{
    Console.WriteLine($"Caller: {i}");
}

IEnumerable<int> ProduceEvenNumbers(int upto)
{
    Console.WriteLine("Iterator: start.");
    for (int i = 0; i <= upto; i += 2)
    {
        Console.WriteLine($"Iterator: about to yield {i}");
        yield return i;
        Console.WriteLine($"Iterator: yielded {i}");
    }
    Console.WriteLine("Iterator: end.");
}
// Output:
// Caller: about to iterate.
// Iterator: start.
// Iterator: about to yield 0
// Caller: 0
// Iterator: yielded 0
// Iterator: about to yield 2
// Caller: 2
// Iterator: yielded 2
// Iterator: about to yield 4
// Caller: 4
// Iterator: yielded 4
// Iterator: end.

前の例に示すように、反復子の結果を反復処理し始めると、最初の yield return ステートメントに達するまで反復子が実行されます。 その後、反復子の実行が中断され、呼び出し元は最初の反復値を取得して処理します。 後続の反復処理のたびに、前の中断の原因となった yield return ステートメントの後に反復子の実行が再開され、次の yield return ステートメントに達するまで続行されます。 反復処理は、コントロールが反復子または yield break ステートメントの末尾に達すると完了します。

C# 言語仕様

詳細については、C# 言語仕様yield ステートメントに関するセクションを参照してください。

関連項目