Warum gibt foreach den Index nicht "von Haus aus"? Und was tun?
\n\nHaben Sie jemals eine foreach-Schleife in C# geschrieben und plötzlich festgestellt: "Ich brauche die Nummer des aktuellen Elements!"? Die Hand greift zur Tastatur, um for (int i = 0; i < list.Count; i++) zu schreiben... Stopp! Was, wenn die Sammlung ein IEnumerable ist, das keinen Indexator hat? Oder Sie arbeiten mit einem HashSet? Hier versagt der gute alte for.
Diese Frage ist ein echter Hit auf Stack Overflow (Millionen von Aufrufen!). Und das zu Recht. foreach ist der König der Sammlungsdurchläufe: prägnant, sicher und funktioniert mit jedem aufzählbaren Typ. Aber sein Fluch ist das Fehlen eines eingebauten Zählers. Heute werden wir das beheben. Ich zeige Ihnen 4 bewährte Methoden, um den Index in foreach zu erhalten, und analysiere ihre Vor- und Nachteile sowie ihre Leistung. Los geht's!
Methode Nr. 1. Klassiker: Externer Zähler
\n\nDie einfachste und intuitivste Methode. Wir führen eine Zählervariable vor der Schleife ein und erhöhen sie im Schleifenkörper.
\n\nList<string> fruits = new List<string>() { "Apfel", "Banane", "Kirsche", "Melone" };\nint index = 0;\nforeach (var fruit in fruits)\n{\n Console.WriteLine($"Index {index}: {fruit}");\n index++;\n}\n\nVorteile:
\n- \n
- Äußerst transparent. Der Code ist sofort lesbar. \n
- Erfordert keine zusätzlichen Bibliotheken oder LINQ. \n
- Funktioniert mit jedem
IEnumerable. \n
Nachteile:
\n- \n
- Man darf nicht vergessen, den Zähler zu deklarieren und zu erhöhen. \n
- Wenn innerhalb der Schleife ein
continuesteht, wird der Zähler trotzdem erhöht (wollen Sie das?). \n - Bei verschachtelten Schleifen kann man leicht den Überblick verlieren, welcher index zu welcher Schleife gehört. \n
Diese Methode ist wie ein Schweizer Taschenmesser: zuverlässig, aber einfach. Für 80% der Aufgaben ist sie mehr als ausreichend.
\n\nMethode Nr. 2. LINQ Select mit Index: Eleganz in einer Zeile
\n\nWenn Sie den funktionalen Stil mögen, hilft LINQ. Die Methode Select() hat eine Überladung, die den Index des Elements übergibt.
List<string> fruits = new List<string>() { "Apfel", "Banane", "Kirsche", "Melone" };\n\nforeach (var (fruit, index) in fruits.Select((item, i) => (item, i)))\n{\n Console.WriteLine($"Index {index}: {fruit}");\n}\n\nBeachten Sie die Dekonstruktion des Tupels direkt im Schleifenkopf. Dies ist eine der schönsten Funktionen von modernem C# (ab Version 7.0).
\n\nVorteile:
\n- \n
- Der Code wird deklarativ: "Nimm die Sammlung, füge jedem Element seinen Index hinzu". \n
- Der Zähler tritt nicht nach außen – er ist innerhalb von LINQ gekapselt. \n
- Lässt sich hervorragend mit Ketten anderer LINQ-Methoden kombinieren. \n
Nachteile:
\n- \n
- Ein geringer Overhead durch die Erstellung von Tupeln. Bei Sammlungen bis zu 10.000 Elementen ist dies unbemerkt, aber bei riesigen Listen kann es kritisch sein. \n
- Erfordert den Import von
System.Linq. \n
Diese Methode ist die Wahl derer, die "sauberen" Code schreiben. Sie ist besonders gut geeignet, wenn der Index nicht zur Steuerung der Schleife, sondern zur Übergabe an eine andere Methode benötigt wird.
\n\nMethode Nr. 3. Indexator über ElementAt(): Wenn die Sammlung ein IEnumerable ist
\n\nEs kommt vor, dass Sie nur ein IEnumerable ohne Indexator haben, aber unbedingt ein Element anhand seiner Nummer erhalten möchten. Technisch könnte man das so machen:
IEnumerable<string> fruits = new List<string>() { "Apfel", "Banane", "Kirsche", "Melone" };\n\nfor (int i = 0; i < fruits.Count(); i++) // Achtung: Das ist schlechter Code!\n{\n var fruit = fruits.ElementAt(i);\n Console.WriteLine($"Index {i}: {fruit}");\n}\n\nStopp! Machen Sie das niemals. Count() und ElementAt() arbeiten bei IEnumerable jeweils mit O(n). Ihre Schleife wird zu O(n²) – quadratischer Komplexität. Bei 1000