Yield return en C# : quand le code fonctionne « paresseusement » et c'est génial
\n\nImaginez que vous écrivez un programme qui doit traiter un million de lignes d'un fichier journal. Vous chargez honnêtement tout dans une liste List<string>, vous la passez à une méthode de filtrage, et puis — boum ! — le programme se fige, la mémoire fuit, et l'utilisateur appuie nerveusement sur Ctrl+Alt+Del.
Ça vous dit quelque chose ? Si vous avez déjà été confronté à une telle situation, il est temps de découvrir la « magie paresseuse » de C# — l'opérateur yield return. Ce n'est pas seulement du sucre syntaxique, mais un outil puissant qui peut changer radicalement votre façon de travailler avec les collections.
Dans cet article, nous allons analyser l'utilisation correcte de yield return avec des exemples concrets, regarder sous le capot et découvrir quand l'utiliser, et quand il vaut mieux s'en abstenir. C'est parti !
\n\nQu'est-ce que yield return et comment ça fonctionne ?
\n\nPour expliquer simplement, yield return est une façon de dire au compilateur : « Hé, je ne veux pas retourner toute la collection d'un coup. Je vais donner les éléments un par un, au fur et à mesure des besoins ». Cette approche s'appelle l'évaluation paresseuse (lazy evaluation).
Voici à quoi ressemble un exemple classique :
\n\npublic IEnumerable<int> GetNumbers()\n{\n yield return 1;\n yield return 2;\n yield return 3;\n}\n\nEn appelant cette méthode, vous n'obtenez pas un tableau [1, 2, 3]. Vous obtenez plutôt un itérateur qui vous donnera les nombres un par un, lorsque vous le demanderez. Chaque appel à MoveNext() avance l'exécution de la méthode jusqu'au prochain yield return.
C'est comme une chaîne de montage : les éléments ne sont produits que lorsqu'ils sont nécessaires au consommateur. Pas de copie inutile, pas de listes géantes en mémoire — seulement un travail propre et efficace.
\n\nPourquoi yield return n'est pas simplement un « retour de liste » ?
\n\nBeaucoup de débutants pensent : « Pourquoi aurais-je besoin de yield return si je peux simplement créer un List et le retourner ? ». Analysons cela avec un exemple concret.
Imaginez que vous devez obtenir les 10 premiers nombres pairs d'une séquence infinie. Avec yield return, cela se fait élégamment :
public IEnumerable<int> GetEvenNumbers()\n{\n int i = 0;\n while (true)\n {\n yield return i;\n i += 2;\n }\n}\n\n// Utilisation\nvar firstTenEven = GetEvenNumbers().Take(10).ToList();\n\nEssayez de faire la même chose avec List<int> — vous allez soit entrer dans une boucle infinie, soit stocker tous les nombres générés en mémoire. Yield return nous permet de travailler avec des séquences potentiellement infinies, sans saturer la mémoire vive.
Exemple pratique : lecture d'un gros fichier
\n\nRevenons à notre fichier journal d'un million de lignes. Voici comment les débutants écrivent généralement :
\n\npublic List<string> ReadLogLines(string path)\n{\n var lines = new List<string>();\n using (var reader = new StreamReader(path))\n {\n string line;\n while ((line = reader.ReadLine()) != null)\n {\n lines.Add(line);\n }\n }\n return lines;\n}\n\nLe problème : tout le fichier est chargé en mémoire. Si le fichier pèse 500 Mo, la liste pèsera à peu près autant. Et si vous avez seulement besoin de filtrer les lignes contenant des erreurs ? Vous chargez quand même tout.
\n\nVoici l'utilisation correcte de yield return :
public IEnumerable<string> ReadLogLines(string path)\n{\n using (var reader = new StreamReader(path))\n {\n string line;\n while ((line = reader.ReadLine()) != null)\n {\n yield return line;\n }\n }\n}\n\n// Filtrage sans charger tout le fichier\nvar errorLines = ReadLogLines("log.txt")\n .Where(line => line.Contains("ERROR"))\n .Take(100);<