istruzione yield: specificare l'elemento successivo
Usare l'istruzione yield
in un iteratore per fornire il valore successivo o segnalare la fine di un'iterazione. L'istruzione yield
presenta le due forme seguenti:
yield return
: per fornire il valore successivo nell'iterazione, come illustrato nell'esempio seguente: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
: per segnalare in modo esplicito la fine dell'iterazione, come illustrato nell'esempio seguente: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; } } }
L'iterazione termina anche quando il controllo raggiunge la fine di un iteratore.
Negli esempi precedenti il tipo restituito di iteratori è IEnumerable<T> (in casi non generici, usare IEnumerable come tipo restituito di un iteratore). È anche possibile usare IAsyncEnumerable<T> come tipo restituito di un iteratore. Questo rende un iteratore asincrono. Usare l'istruzione await foreach
per iterare il risultato dell'iteratore, come illustrato nell'esempio seguente:
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> o IEnumerator può anche essere il tipo restituito di un iteratore. Usare questi tipi restituiti quando si implementa il metodo GetEnumerator
negli scenari seguenti:
Si progetta il tipo che implementa l’interfaccia IEnumerable<T> o IEnumerable.
Si aggiunge un'istanza o un metodo di estensione
GetEnumerator
per abilitare l'iterazione sull'istanza del tipo con l'istruzioneforeach
, come illustrato nell'esempio seguente: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; } }
Non è possibile usare le istruzioni yield
in:
- metodi con parametri in, ref o out
- espressioni lambda e metodi anonimi
- blocchi non sicuri. Prima di C# 13,
yield
non era valido in nessun metodo con un bloccounsafe
. A partire da C# 13, è possibile usareyield
nei metodi con blocchiunsafe
, ma non nel bloccounsafe
. yield return
eyield break
non possono essere usati nei blocchi blocchi try, catch e finally .
Esecuzione di un iteratore
La chiamata di un iteratore non porta alla sua esecuzione immediata, come illustrato nell'esempio seguente:
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.
Come illustrato nell'esempio precedente, quando si inizia a scorrere il risultato di un iteratore, un iteratore viene eseguito fino a quando non viene raggiunta la prima istruzione yield return
. L'esecuzione di un iteratore viene quindi sospesa e il chiamante ottiene il primo valore di iterazione e lo elabora. In ogni iterazione successiva, l'esecuzione di un iteratore riprende dopo l'istruzione yield return
che ha causato la sospensione precedente e continua fino al raggiungimento dell'istruzione yield return
successiva. L'iterazione viene completata quando il controllo raggiunge la fine di un iteratore o di un'istruzione yield break
.
Specifiche del linguaggio C#
Per altre informazioni, vedere la sezione Istruzione yield della specifica del linguaggio C#.