Entrainement
Module
Apprenez à créer des variables de tableau et à effectuer une itération à travers des éléments du tableau.
Ce navigateur n’est plus pris en charge.
Effectuez une mise à niveau vers Microsoft Edge pour tirer parti des dernières fonctionnalités, des mises à jour de sécurité et du support technique.
Presque chaque programme que vous écrivez doit itérer au sein d’une collection. Vous allez écrire du code qui examine chaque élément d’une collection.
Vous allez également créer des méthodes d’itérateur, c’est-à-dire des méthodes qui produisent un itérateur pour les éléments de cette classe. Un itérateur est un objet qui parcourt un conteneur, notamment des listes. Les itérateurs peuvent être utilisés pour :
Le langage C# fournit des fonctionnalités permettant de générer et de consommer des séquences. Ces séquences peuvent être produites et consommées de manière synchrone ou asynchrone. Cet article présente une vue d’ensemble de ces fonctionnalités.
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 :
foreach (var item in collection)
{
Console.WriteLine(item?.ToString());
}
C'est tout. Pour itérer au sein du contenu d’une collection, l’instruction foreach
suffit. L’instruction foreach
n’est cependant pas magique. Cette méthode 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 à l’itération d’une collection : IEnumerable<T>
et IEnumerator<T>
. Ce mécanisme est expliqué plus en détail ci-dessous.
Ces deux interfaces ont également des contreparties non génériques : IEnumerable
et IEnumerator
. Les versions génériques conviennent mieux pour un code moderne.
Lorsqu’une séquence est générée de manière asynchrone, vous pouvez utiliser l’instruction await foreach
pour consommer la séquence de manière asynchrone :
await foreach (var item in asyncSequence)
{
Console.WriteLine(item?.ToString());
}
Lorsqu’une séquence correspond à System.Collections.Generic.IEnumerable<T>, vous utilisez foreach
. Lorsqu’une séquence correspond à System.Collections.Generic.IAsyncEnumerable<T>, vous utilisez await foreach
. Dans ce dernier cas, la séquence est générée de manière asynchrone.
Une autre fonctionnalité intéressante du langage C# vous permet de construire des méthodes qui créent une source pour une énumération. Ces méthodes sont appelées des méthodes d’itérateur. Une méthode d’itérateur définit comment générer les objets dans une séquence sur demande. Vous utilisez les mots clés contextuels yield return
pour définir une méthode d’itérateur.
Vous pouvez par exemple écrire cette méthode pour produire la séquence d’entiers de 0 à 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. 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. La définition de méthode ci-dessous produit exactement la même séquence de nombres :
public IEnumerable<int> GetSingleDigitNumbersLoop()
{
int index = 0;
while (index < 10)
yield return index++;
}
Vous n’êtes pas obligé de choisir l’une ou l’autre. Vous pouvez avoir autant d’instructions yield return
que nécessaire pour répondre aux besoins de votre méthode :
public IEnumerable<int> GetSetsOfNumbers()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
index = 100;
while (index < 110)
yield return index++;
}
Tous les exemples précédents auraient un équivalent asynchrone. Dans chaque cas, vous devez remplacer le type de retour de IEnumerable<T>
par IAsyncEnumerable<T>
. Par exemple, la version asynchrone de l’exemple précédent serait la suivante :
public async IAsyncEnumerable<int> GetSetsOfNumbersAsync()
{
int index = 0;
while (index < 10)
yield return index++;
await Task.Delay(500);
yield return 50;
await Task.Delay(500);
index = 100;
while (index < 110)
yield return index++;
}
Il s’agit de la syntaxe des itérateurs synchrones et asynchrones. Prenons un exemple concret. 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. 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. Cette petite méthode d’itérateur peut effectuer ce travail :
public static IEnumerable<T> Sample<T>(this IEnumerable<T> sourceSequence, int interval)
{
int index = 0;
foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}
Si la lecture à partir de l’appareil IoT produit une séquence asynchrone, vous devez modifier la méthode comme indiqué dans la méthode suivante :
public static async IAsyncEnumerable<T> Sample<T>(this IAsyncEnumerable<T> sourceSequence, int interval)
{
int index = 0;
await foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}
Il existe une restriction importante concernant 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. Le code suivant ne sera pas compilé :
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. 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
.
Vous pouvez modifier légèrement la dernière méthode pour utiliser yield return
partout :
public IEnumerable<int> GetFirstDecile()
{
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. Une qui utilise return
et l’autre qui utilise yield return
. Imaginons que vous vouliez renvoyer une collection vide, ou les cinq premiers nombres impairs, en vous basant sur un argument booléen. Vous pouvez écrire ceci sous la forme de ces deux méthodes :
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. La première utilise l’instruction return
standard pour retourner une collection vide ou l’itérateur créé par la deuxième méthode. La deuxième méthode utilise l’instruction yield return
pour créer la séquence demandée.
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. Cette méthode réduit également les erreurs que font les développeurs en ne gérant pas correctement les ressources.
Le compilateur traduit la boucle foreach
présentée dans le premier exemple en quelque chose de similaire à cette construction :
IEnumerator<int> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
Le code exact généré par le compilateur est plus compliqué et gère les cas où l’objet renvoyé par GetEnumerator()
implémente l’interface IDisposable
. L’expansion complète génère un code qui ressemble davantage à celui-ci :
{
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of enumerator.
}
}
Le compilateur traduit le premier exemple asynchrone en quelque chose de similaire à cette construction :
{
var enumerator = collection.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of async enumerator.
}
}
La manière dont l’énumérateur est éliminé dépend des caractéristiques du type d’enumerator
. Dans le cas synchrone général, la clause finally
se développe en :
finally
{
(enumerator as IDisposable)?.Dispose();
}
Le cas asynchrone général se développe en :
finally
{
if (enumerator is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
}
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
ou IAsyncDisposable
, la clause finally
se développe en un bloc vide :
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 :
finally
{
((IDisposable)enumerator).Dispose();
}
Heureusement, vous n’avez pas besoin de mémoriser tous ces détails. L’instruction foreach
gère toutes ces subtilités pour vous. Le compilateur génère le code correct pour toutes ces constructions.
Commentaires sur .NET
.NET est un projet open source. Sélectionnez un lien pour fournir des commentaires :
Entrainement
Module
Apprenez à créer des variables de tableau et à effectuer une itération à travers des éléments du tableau.
Documentation
Itérer dans les collections - C#
Découvrez comment utiliser un itérateur pour parcourir des collections telles que des listes et des tableaux. Les itérateurs sont consommés à partir du code client à l’aide d’une instruction foreach ou d’une requête LINQ.
Classes et méthodes génériques - C#
En savoir plus sur les génériques. Les types génériques optimisent la réutilisation du code, la sécurité des types et le niveau de performance et sont couramment utilisés pour créer des classes de collection.
Indexeurs - Guide de programmation C#
Les indexeurs en C# permettent aux instances de classe ou de struct d’être indexées comme des tableaux. Vous pouvez définir ou obtenir la valeur indexée sans spécifier de type ou de membre d’instance.