Pourquoi foreach ne fournit-il pas l'index "prêt à l'emploi" ? Et que faire ?
\n\nAvez-vous déjà écrit une boucle foreach en C# et réalisé soudainement : "J'ai besoin du numéro de l'élément courant !". La main se tend vers le clavier pour écrire for (int i = 0; i < list.Count; i++)... Stop ! Et si la collection est un IEnumerable qui n'a pas d'indexeur ? Ou si vous travaillez avec un HashSet ? Le bon vieux for est alors impuissant.
Cette question est un véritable succès sur Stack Overflow (des millions de vues !). Et ce n'est pas pour rien. foreach est le roi du parcours de collections : il est concis, sûr et fonctionne avec tout type énumérable. Mais sa malédiction est l'absence de compteur intégré. Aujourd'hui, nous allons y remédier. Je vais vous montrer 4 méthodes éprouvées pour obtenir l'index dans foreach, analyser leurs avantages, inconvénients et performances. C'est parti !
Méthode n°1. La classique : compteur externe
\n\nLa méthode la plus simple et intuitive. On déclare une variable compteur avant la boucle et on l'incrémente dans le corps.
\n\nList<string> fruits = new List<string>() { "Pomme", "Banane", "Cerise", "Melon" };\nint index = 0;\nforeach (var fruit in fruits)\n{\n Console.WriteLine($"Index {index}: {fruit}");\n index++;\n}\n\nAvantages :
\n- \n
- Extrêmement clair. Le code se lit en une seconde. \n
- Ne nécessite ni bibliothèques supplémentaires ni LINQ. \n
- Fonctionne avec tout
IEnumerable. \n
Inconvénients :
\n- \n
- Il ne faut pas oublier de déclarer et d'incrémenter le compteur. \n
- S'il y a un
continuedans la boucle, le compteur s'incrémente quand même (est-ce ce que vous voulez ?). \n - Avec des boucles imbriquées, il est facile de se perdre sur l'index qui se rapporte à quoi. \n
Cette méthode est comme un couteau suisse : fiable, mais basique. Pour 80% des tâches, elle est plus que suffisante.
\n\nMéthode n°2. LINQ Select avec index : l'élégance en une ligne
\n\nSi vous aimez le style fonctionnel, LINQ vient à la rescousse. La méthode Select() a une surcharge qui transmet l'index de l'élément.
List<string> fruits = new List<string>() { "Pomme", "Banane", "Cerise", "Melon" };\n\nforeach (var (fruit, index) in fruits.Select((item, i) => (item, i)))\n{\n Console.WriteLine($"Index {index}: {fruit}");\n}\n\nRemarquez la déconstruction de tuple directement dans l'en-tête de la boucle. C'est l'une des plus belles fonctionnalités du C# moderne (depuis la version 7.0).
\n\nAvantages :
\n- \n
- Le code devient déclaratif : "prends la collection, ajoute à chaque élément son index". \n
- Le compteur ne fuit pas à l'extérieur – il est encapsulé dans LINQ. \n
- S'associe parfaitement avec d'autres méthodes LINQ en chaîne. \n
Inconvénients :
\n- \n
- Un léger overhead dû à la création de tuples. Pour des collections jusqu'à 10 000 éléments, c'est imperceptible, mais pour des listes géantes, cela peut être critique. \n
- Nécessite l'import de
System.Linq. \n
Cette méthode est le choix de ceux qui écrivent du code "propre". Elle est particulièrement adaptée lorsque l'index n'est pas nécessaire pour contrôler la boucle, mais pour être transmis à une autre méthode.
\n\nMéthode n°3. Indexeur via ElementAt() : quand la collection est un IEnumerable
\n\nIl arrive que vous n'ayez qu'un IEnumerable, sans indexeur, mais que vous souhaitiez vraiment obtenir un élément par son numéro. Techniquement, on pourrait faire ceci :
IEnumerable<string> fruits = new List<string>() { "Pomme", "Banane", "Cerise", "Melon" };\n\nfor (int i = 0; i < fruits.Count(); i++) // Attention : c'est du mauvais code !\n{\n var fruit = fruits.ElementAt(i);\n Console.WriteLine($"Index {i}: {fruit}");\n}\n\nStop ! Ne faites jamais cela. Count() et ElementAt() pour IEnumerable fonctionnent chacun en O(n). Votre boucle deviendra en O(n²) – une complexité quadratique. Sur 1000