IteratorsIterators

Quasi tutti i programmi che vengono scritti avranno la necessità di eseguire un'iterazione in una raccolta.Almost every program you write will have some need to iterate over a collection. Si scriverà il codice che esamina ogni elemento in una raccolta.You'll write code that examines every item in a collection.

Si creeranno anche metodi iteratori che sono metodi che producono un iteratore (ovvero un oggetto che attraversa un contenitore, in particolare gli elenchi) per gli elementi di tale classe.You'll also create iterator methods which are methods that produces an iterator (which is an object that traverses a container, particularly lists) for the elements of that class. Essi possono essere usati per le azioni seguenti:These can be used for:

  • Esecuzione di un'azione su ogni elemento in una raccolta.Performing an action on each item in a collection.
  • Enumerazione di una raccolta personalizzata.Enumerating a custom collection.
  • Estensione in LINQ o in altre librerie.Extending LINQ or other libraries.
  • Creazione di una pipeline di dati dove i dati fluiscono in modo efficace tramite i metodi iteratori.Creating a data pipeline where data flows efficiently through iterator methods.

Il linguaggio C# offre funzionalità per entrambi gli scenari.The C# language provides features for both these scenarios. In questo articolo viene offerta una panoramica di tali funzionalità.This article provides an overview of those features.

Questa esercitazione prevede diversi passaggi.This tutorial has multiple steps. Dopo ogni passaggio, è possibile eseguire l'applicazione e verificare lo stato di avanzamento.After each step, you can run the application and see the progress. È anche possibile visualizzare o scaricare l'esempio completato per questo argomento.You can also view or download the completed sample for this topic. Per istruzioni sul download, vedere Esempi ed esercitazioni.For download instructions, see Samples and Tutorials.

Iterazione con foreachIterating with foreach

L'enumerazione di una raccolta è semplice: la parola chiave foreach enumera una raccolta eseguendo l'istruzione incorporata una volta per ogni elemento nella raccolta:Enumerating a collection is simple: The foreach keyword enumerates a collection, executing the embedded statement once for each element in the collection:

foreach (var item in collection)
{
   Console.WriteLine(item.ToString());
}

Tutto qui.That's all there is to it. Per eseguire l'iterazione in tutto il contenuto di una raccolta, è sufficiente l'istruzione foreach.To iterate over all the contents of a collection, the foreach statement is all you need. L'istruzione foreach non è tuttavia magica.The foreach statement isn't magic, though. Si basa su due interfacce generiche definite nella libreria di base di .NET per generare il codice necessario per eseguire l'iterazione di una raccolta: IEnumerable<T> e IEnumerator<T>.It relies on two generic interfaces defined in the .NET core library in order to generate the code necessary to iterate a collection: IEnumerable<T> and IEnumerator<T>. Questo meccanismo è illustrato più dettagliatamente nel seguito di questo articolo.This mechanism is explained in more detail below.

Queste due interfacce dispongono anche di controparti non generiche: IEnumerable e IEnumerator.Both of these interfaces also have non-generic counterparts: IEnumerable and IEnumerator. Le versioni generic sono preferite per il codice moderno.The generic versions are preferred for modern code.

Origini di enumerazione con metodi iteratoriEnumeration sources with iterator methods

Un'altra eccezionale funzionalità del linguaggio C# consente di creare metodi che creano un'origine per un'enumerazione.Another great feature of the C# language enables you to build methods that create a source for an enumeration. Essi sono denominati metodi iteratori.These are referred to as iterator methods. Un metodo iteratore definisce come generare gli oggetti in una sequenza quando richiesto.An iterator method defines how to generate the objects in a sequence when requested. Usare le parole chiave contestuali yield return per definire un metodo iteratore.You use the yield return contextual keywords to define an iterator method.

È possibile scrivere questo metodo per produrre la sequenza di numeri interi da 0 a 9:You could write this method to produce the sequence of integers from 0 through 9:

public IEnumerable<int> GetSingleDigitNumbers()
{
    yield return 0;
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
}

Il codice sopra riportato indica istruzioni yield return distinte per evidenziare che è possibile usare più istruzioni yield return discrete in un metodo iteratore.The code above shows distinct yield return statements to highlight the fact that you can use multiple discrete yield return statements in an iterator method. È possibile (come capita spesso) usare altri costrutti di linguaggio per semplificare il codice di un metodo iteratore.You can (and often do) use other language constructs to simplify the code of an iterator method. La definizione di metodo seguente produce la stessa identica sequenza di numeri:The method definition below produces the exact same sequence of numbers:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index < 10)
        yield return index++;
}

Non è necessario scegliere l'uno o l'altro.You don't have to decide one or the other. È possibile specificare qualsiasi numero di istruzioni yield return necessarie per soddisfare le esigenze del metodo:You can have as many yield return statements as necessary to meet the needs of your method:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index < 10)
        yield return index++;

    yield return 50;

    index = 100;
    while (index < 110)
        yield return index++;
}

Questa è la sintassi di base.That's the basic syntax. Si consideri un esempio reale in cui si vuole scrivere un metodo iteratore.Let's consider a real world example where you would write an iterator method. Si supponga di lavorare su un progetto IoT e che i sensori dispositivo generino un flusso di dati molto grande.Imagine you're on an IoT project and the device sensors generate a very large stream of data. Per ottenere un'idea dei dati, è possibile scrivere un metodo che campioni ogni elemento dati n.To get a feel for the data, you might write a method that samples every Nth data element. Questo piccolo metodo iteratore serve allo scopo:This small iterator method does the trick:

public static IEnumerable<T> Sample(this IEnumerable<T> sourceSequence, int interval)
{
    int index = 0;
    foreach (T item in sourceSequence)
    {
        if (index++ % interval == 0)
            yield return item;
    }
}

I metodi iteratori presentano un'importante restrizione: non è possibile avere sia un'istruzione return che un'istruzione yield return nello stesso metodo.There is one important restriction on iterator methods: you can't have both a return statement and a yield return statement in the same method. Il codice seguente non verrà compilato:The following will not compile:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index < 10)
        yield return index++;

    yield return 50;

    // generates a compile time error:
    var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
    return items;
}

Questa restrizione non costituisce in genere un problema.This restriction normally isn't a problem. È possibile scegliere di usare yield return in tutto il metodo o di separare il metodo originale in più metodi, alcuni con l'uso di return e altri con l'uso di yield return.You have a choice of either using yield return throughout the method, or separating the original method into multiple methods, some using return, and some using yield return.

È possibile modificare leggermente l'ultimo metodo in modo da usare yield return ovunque:You can modify the last method slightly to use yield return everywhere:

public IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index < 10)
        yield return index++;

    yield return 50;

    var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
    foreach (var item in items)
        yield return item;
}

In alcuni casi, la risposta giusta è la suddivisione di un metodo iteratore in due metodi diversi:Sometimes, the right answer is to split an iterator method into two different methods. uno che usa return e un altro che usa yield return.One that uses return, and a second that uses yield return. Si consideri una situazione in cui si vuole restituire una raccolta vuota o i primi 5 numeri dispari, in base a un argomento booleano.Consider a situation where you might want to return an empty collection, or the first 5 odd numbers, based on a boolean argument. Ciò può essere scritto con i due metodi seguenti:You could write that as these two methods:

public IEnumerable<int> GetSingleDigitOddNumbers(bool getCollection)
{
    if (getCollection == false)
        return new int[0];
    else
        return IteratorMethod();
}

private IEnumerable<int> IteratorMethod()
{
    int index = 0;
    while (index < 10)
    {
        if (index % 2 == 1)
            yield return index;
        index++;
    }
}

Esaminare i metodi descritti precedentemente.Look at the methods above. Il primo usa l'istruzione return standard per restituire una raccolta vuota o l'iteratore creato dal secondo metodo.The first uses the standard return statement to return either an empty collection, or the iterator created by the second method. Il secondo metodo usa l'istruzione yield return per creare la sequenza richiesta.The second method uses the yield return statement to create the requested sequence.

Approfondimento di foreachDeeper Dive into foreach

L'istruzione foreach si espande in un termine standard che usa le interfacce IEnumerable<T> e IEnumerator<T> per eseguire l'iterazione tra tutti gli elementi di una raccolta.The foreach statement expands into a standard idiom that uses the IEnumerable<T> and IEnumerator<T> interfaces to iterate across all elements of a collection. Riduce al minimo gli errori commessi dagli sviluppatori a causa di una gestione non corretta delle risorse.It also minimizes errors developers make by not properly managing resources.

Il compilatore traduce il ciclo foreach illustrato nel primo esempio in un risultato simile a questo costrutto:The compiler translates the foreach loop shown in the first example into something similar to this construct:

IEnumerator<int> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
    var item = enumerator.Current;
    Console.WriteLine(item.ToString());
}

Il costrutto sopra rappresenta il codice generato dal compilatore C# a partire dalla versione 5 e successive.The construct above represents the code generated by the C# compiler as of version 5 and above. Prima della versione 5, la variabile item aveva un ambito diverso:Prior to version 5, the item variable had a different scope:

// C# versions 1 through 4:
IEnumerator<int> enumerator = collection.GetEnumerator();
int item = default(int);
while (enumerator.MoveNext())
{
    item = enumerator.Current;
    Console.WriteLine(item.ToString());
}

La modifica è stata apportata perché il comportamento precedente avrebbe potuto causare bug gravi e difficili da diagnosticare relativi alle espressioni lambda.This was changed because the earlier behavior could lead to subtle and hard to diagnose bugs involving lambda expressions. Per altre informazioni sulle espressioni lambda, vedere Espressioni lambda.For more information about lambda expressions, see Lambda expressions.

Il codice esatto generato dal compilatore è leggermente più complesso e gestisce situazioni in cui l'oggetto restituito da GetEnumerator() implementa l'interfaccia IDisposable.The exact code generated by the compiler is somewhat more complicated, and handles situations where the object returned by GetEnumerator() implements the IDisposable interface. L'espansione completa genera codice più simile al seguente:The full expansion generates code more like this:

{
    var enumerator = collection.GetEnumerator();
    try
    {
        while (enumerator.MoveNext())
        {
            var item = enumerator.Current;
            Console.WriteLine(item.ToString());
        }
    } finally
    {
        // dispose of enumerator.
    }
}

Il modo in cui l'enumeratore viene eliminato dipende dalle caratteristiche del tipo di enumerator.The manner in which the enumerator is disposed of depends on the characteristics of the type of enumerator. Nel caso generale, la clausola finally si espande a:In the general case, the finally clause expands to:

finally
{
   (enumerator as IDisposable)?.Dispose();
}

Tuttavia, se il tipo di enumerator è un tipo sealed e non esiste alcuna conversione implicita dal tipo di enumerator a IDisposable, la clausola finally si espande in un blocco vuoto:However, if the type of enumerator is a sealed type and there is no implicit conversion from the type of enumerator to IDisposable, the finally clause expands to an empty block:

finally
{
}

Se esiste una conversione implicita dal tipo di enumerator a IDisposable e enumerator è un tipo di valore non nullable, la finally si clausola espande a:If there is an implicit conversion from the type of enumerator to IDisposable, and enumerator is a non-nullable value type, the finally clause expands to:

finally
{
   ((IDisposable)enumerator).Dispose();
}

Fortunatamente, non è necessario ricordare tutti questi dettagli.Thankfully, you don't need to remember all these details. L'istruzione foreach gestisce tutte queste sfumature.The foreach statement handles all those nuances for you. Il compilatore genererà il codice corretto per tutti questi costrutti.The compiler will generate the correct code for any of these constructs.