IteratorsIterators

Presque chaque programme que vous écrivez doit itérer au sein d’une collection.Almost every program you write will have some need to iterate over a collection. Vous allez écrire du code qui examine chaque élément d’une collection.You'll write code that examines every item in a collection.

Vous allez également créer des méthodes d’itérateur, qui sont des méthodes produisant un itérateur pour les éléments de cette classe.You'll also create iterator methods which are methods that produces an iterator for the elements of that class. Vous pouvez les utiliser pour :These can be used for:

  • Effectuer une action sur chaque élément d’une collection.Performing an action on each item in a collection.
  • Énumérer une collection personnalisée.Enumerating a custom collection.
  • Étendre LINQ ou d’autres bibliothèques.Extending LINQ or other libraries.
  • Créer un pipeline de données où les données circulent efficacement via des méthodes d’itérateur.Creating a data pipeline where data flows efficiently through iterator methods.

Le langage C# fournit des fonctionnalités pour ces deux scénarios.The C# language provides features for both these scenarios. Cet article présente une vue d’ensemble de ces fonctionnalités.This article provides an overview of those features.

Ce didacticiel comporte plusieurs étapes.This tutorial has multiple steps. Après chaque étape, vous pourrez exécuter l’application et voir la progression.After each step, you can run the application and see the progress. Vous pouvez également afficher ou télécharger l’exemple complet pour cette rubrique.You can also view or download the completed sample for this topic. Pour obtenir des instructions de téléchargement, consultez Exemples et didacticiels.For download instructions, see Samples and Tutorials.

Itération avec foreachIterating with foreach

L’énumération d’une collection est simple : le mot clé foreach énumère une collection en exécutant l’instruction incorporée une fois pour chaque élément de la collection :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());
}

C’est aussi simple que cela.That's all there is to it. Pour itérer au sein du contenu d’une collection, l’instruction foreach suffit.To iterate over all the contents of a collection, the foreach statement is all you need. L’instruction foreach n’est cependant pas magique.The foreach statement isn't magic, though. Elle s’appuie sur deux interfaces génériques définies dans la bibliothèque .NET Core afin de générer le code nécessaire pour itérer au sein d’une collection : IEnumerable<T> et 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>. Ce mécanisme est expliqué plus en détail ci-dessous.This mechanism is explained in more detail below.

Ces deux interfaces ont également des contreparties non génériques : IEnumerable et IEnumerator.Both of these interfaces also have non-generic counterparts: IEnumerable and IEnumerator. Les versions génériques conviennent mieux pour un code moderne.The generic versions are preferred for modern code.

Sources d’énumération avec des méthodes d’itérateurEnumeration sources with iterator methods

Une autre fonctionnalité intéressante du langage C# vous permet de construire des méthodes qui créent une source pour une énumération.Another great feature of the C# language enables you to build methods that create a source for an enumeration. Elles sont appelées méthodes d’itérateur.These are referred to as iterator methods. Une méthode d’itérateur définit comment générer les objets dans une séquence sur demande.An iterator method defines how to generate the objects in a sequence when requested. Vous utilisez les mots clés contextuels yield return pour définir une méthode d’itérateur.You use the yield return contextual keywords to define an iterator method.

Vous pouvez par exemple écrire cette méthode pour produire la séquence d’entiers de 0 à 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;
}

Le code ci-dessus montre des instructions yield return distinctes pour mettre en évidence le fait que vous pouvez utiliser plusieurs instructions yield return discrètes dans une méthode d’itérateur.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. Vous pouvez (et c’est souvent le cas) utiliser d’autres constructions du langage pour simplifier le code d’une méthode d’itérateur.You can (and often do) use other language constructs to simplify the code of an iterator method. La définition de méthode ci-dessous produit exactement la même séquence de nombres :The method definition below produces the exact same sequence of numbers:

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

Vous n’êtes pas obligé de choisir l’une ou l’autre.You don't have to decide one or the other. Vous pouvez avoir autant d’instructions yield return que nécessaire pour répondre aux besoins de votre méthode :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++;
}

Il s’agit de la syntaxe de base.That's the basic syntax. Prenons un exemple réel où vous devez écrire une méthode d’itérateur.Let's consider a real world example where you would write an iterator method. Imaginez que vous êtes sur un projet IoT et que les capteurs d’un appareil génèrent un flux de données très important.Imagine you're on an IoT project and the device sensors generate a very large stream of data. Pour obtenir un aperçu des données, vous pouvez écrire une méthode qui échantillonne les éléments de données à un certain intervalle.To get a feel for the data, you might write a method that samples every Nth data element. Cette petite méthode d’itérateur peut effectuer ce travail :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;
    }
}

Il existe une restriction importante sur les méthodes d’itérateur : vous ne pouvez pas avoir à la fois une instruction return et une instruction yield return dans la même méthode.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. Ce qui suit ne peut pas être compilé :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;
}

Cette restriction n’est normalement pas un problème.This restriction normally isn't a problem. Vous avez le choix entre utiliser yield return dans toute la méthode ou de diviser la méthode d’origine en plusieurs méthodes, certaines utilisant return et d’autres utilisant 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.

Vous pouvez modifier légèrement la dernière méthode pour utiliser yield return partout :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;
}

Parfois, la bonne réponse est de fractionner une méthode d’itérateur en deux méthodes différentes.Sometimes, the right answer is to split an iterator method into two different methods. Une qui utilise return et l’autre qui utilise yield return.One that uses return, and a second that uses yield return. Considérez une situation où vous voulez retourner une collection vide, ou bien les 5 premiers nombres impairs, en vous basant sur un argument booléen.Consider a situation where you might want to return an empty collection, or the first 5 odd numbers, based on a boolean argument. Vous pouvez écrire ceci sous la forme de ces deux méthodes :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++;
    }
}

Examinez les méthodes ci-dessus.Look at the methods above. La première utilise l’instruction return standard pour retourner une collection vide ou l’itérateur créé par la deuxième méthode.The first uses the standard return statement to return either an empty collection, or the iterator created by the second method. La deuxième méthode utilise l’instruction yield return pour créer la séquence demandée.The second method uses the yield return statement to create the requested sequence.

Aller plus loin avec foreachDeeper Dive into foreach

L’instruction foreach se développe en un idiome standard qui utilise les interfaces IEnumerable<T> et IEnumerator<T> pour itérer à travers tous les éléments d’une collection.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. Elle réduit également les erreurs que font les développeurs en ne gérant pas correctement les ressources.It also minimizes errors developers make by not properly managing resources.

Le compilateur traduit la boucle foreach présentée dans le premier exemple en quelque chose de similaire à cette construction :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());
}

La construction ci-dessus représente le code généré par le compilateur C# dans la version 5 et ultérieure.The construct above represents the code generated by the C# compiler as of version 5 and above. Avant la version 5, la variable item avait une étendue différente :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());
}

Ceci a été changé, car le comportement antérieur pouvait entraîner des bogues subtils et difficiles à diagnostiquer impliquant des expressions lambda.This was changed because the earlier behavior could lead to subtle and hard to diagnose bugs involving lambda expressions. Pour plus d’informations sur les expressions lambda, voir Expressions lambda.For more information about lambda expressions, see Lambda expressions.

Le code exact généré par le compilateur est un peu plus compliqué et gère les cas où l’objet retourné par GetEnumerator() implémente l’interface 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’expansion complète génère un code qui ressemble davantage à celui-ci :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.
    }
}

La manière dont l’énumérateur est éliminé dépend des caractéristiques du type d’enumerator.The manner in which the enumerator is disposed of depends on the characteristics of the type of enumerator. En règle générale, la clause finally se développe en :In the general case, the finally clause expands to:

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

Cependant, si le type d’enumerator est un type sealed et qu’il n’existe pas de conversion implicite du type d’enumerator en IDisposable, la clause finally se développe en un bloc vide :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
{
}

S’il existe une conversion implicite du type d’enumerator en IDisposable et si enumerator est un type valeur qui n’autorise pas les valeurs Null, la clause finally se développe en :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();
}

Heureusement, vous n’avez pas besoin de mémoriser tous ces détails.Thankfully, you don't need to remember all these details. L’instruction foreach gère toutes ces subtilités pour vous.The foreach statement handles all those nuances for you. Le compilateur génère le code correct pour toutes ces constructions.The compiler will generate the correct code for any of these constructs.