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 int[] {2, 3, 4, 5, -1, 3, 4}))); // Output: 2 3 4 5 Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {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
ステートメントを使うことはできません。
- in、ref、または out パラメーターを持つメソッド
- ラムダ式および匿名メソッド
- アンセーフ ブロック。 C# 13 より前の
yield
は、unsafe
ブロックを含むメソッドでは無効でした。 C# 13 以降では、unsafe
ブロックを含むメソッドでyield
を使用することはできますが、unsafe
ブロック内ではそれができません。 yield return
およびyield break
は、catchおよびfinally ブロック、または対応するcatch
ブロックを持つ try ブロックでは使用できません。yield return
ステートメントとyield break
ステートメントは、catch
ブロックを持たないtry
ブロックで使用でき、finally
ブロックでのみ使用できます。
反復子の実行
次の例に示すように、反復子の呼び出しはすぐには実行されません。
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 ステートメントに関するセクションを参照してください。
関連項目
.NET