Pourquoi avez-vous besoin de yield en C# ? Le secret des collections paresseuses
Imaginez : vous écrivez une méthode qui doit retourner une liste de nombres de 1 à un million. La première chose qui vient à l'esprit est de créer un List<int>, de le remplir et de le retourner. Mais que faire si le code appelant ne veut que les 10 premiers éléments ? Vous avez quand même passé du temps à créer un million d'objets, occupé de la mémoire – et tout cela pour rien.
C'est là qu'intervient le mot-clé yield en C#. Ce n'est pas seulement du sucre syntaxique, mais un outil puissant pour créer des séquences paresseuses (lazy). Dans cet article, nous allons décortiquer yield return et yield break avec des exemples réels, comprendre comment ils fonctionnent sous le capot, et apprendre à les utiliser dans le développement quotidien.
Ce tutoriel est basé sur une question réelle de Stack Overflow qui a recueilli des milliers de vues. Nous l'avons adapté pour un contenu éducatif, en ajoutant des exemples de code C# pas à pas et des conseils pratiques utiles.
Qu'est-ce que yield en C# ? En termes simples
yield est un mot-clé contextuel en C# qui indique au compilateur : « Cette méthode est un itérateur ». Lorsque vous écrivez yield return, la méthode n'exécute pas tout d'un coup. Au lieu de cela, elle retourne les éléments un par un, lorsque le code appelant les demande. C'est ce qu'on appelle l'exécution paresseuse.
Contrairement à un return normal qui termine la méthode, yield return met la méthode en pause, mémorise son état et retourne l'élément courant. Lors du prochain appel, la méthode reprend là où elle s'était arrêtée.
Syntaxe : yield return et yield break
En C#, il existe deux formes de yield :
- yield return – retourne l'élément suivant de la séquence.
- yield break – termine immédiatement l'itération (comme
breakdans une boucle).
La méthode qui utilise yield doit retourner l'un de ces types :
IEnumerable<T>IEnumerableIAsyncEnumerable<T>(à partir de C# 8.0)IEnumerator<T>ouIEnumerator
Premier exemple : du simple au complexe
Écrivons un itérateur très simple qui génère les nombres de 1 à 5 :
public static IEnumerable<int> GetNumbers(){ yield return 1; yield return 2; yield return 3; yield return 4; yield return 5;}
// Utilisation :foreach (int number in GetNumbers()){ Console.WriteLine(number);}Sortie :
12345À première vue, cela ressemble à une liste normale. Mais la différence est énorme : chaque élément est généré uniquement lorsque foreach le demande. Aucune allocation mémoire pour la liste entière !
Comment cela fonctionne-t-il sous le capot ?
Le compilateur C# transforme la méthode avec yield en un véritable automate d'états (state machine). Il crée une classe qui implémente IEnumerable<T> et IEnumerator<T>. Cette classe stocke :
- L'état courant (sur quelle ligne de la méthode nous nous sommes arrêtés).
- Les variables locales de la méthode.
- Une référence à l'objet courant (
this), si la méthode n'est pas statique.
Chaque fois que MoveNext() est appelé (ce que fait foreach), l'automate d'états reprend l'exécution là où il s'était arrêté.
Exemple réel : lecture d'un gros fichier
Imaginez que vous devez traiter un fichier de logs de 10 Go. Si vous le lisez entièrement en mémoire, le programme plantera avec une OutOfMemoryException. Avec yield, vous pouvez lire le fichier ligne par ligne et traiter chaque ligne à la volée :
public static IEnumerable<s