Condividi tramite


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'istruzione foreach , 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 blocco unsafe. A partire da C# 13, è possibile usare yield nei metodi con blocchi unsafe, ma non nel blocco unsafe.
  • yield return e yield break non possono essere usati nei blocchi catch e finally oppure in blocchi try con un blocco corrispondente catch . Le yield return istruzioni e yield break possono essere usate in un try blocco senza catch blocchi, ma solo in un finally blocco.

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#.

Vedi anche