1. Die Evolution des Schmerzes
Du hast das bestimmt schon mal erlebt: Du musst nicht nur durch eine Collection laufen, sondern auch noch die Nummer jedes Elements wissen. Zum Beispiel, um eine nummerierte Liste auszugeben, Elemente mit nicht-null Index zu ändern oder mit jedem dritten was zu machen. Im klassischen for — easy:
// Alles wie gewohnt, C#-Klassiker:
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine($"{i}: {list[i]}");
}
Aber sobald du mit LINQ loslegst, verlierst du plötzlich den Index! All diese schicken .Where, .Select, .OrderBy geben dir nur das Element — aber nicht die Nummer. Natürlich hättest du gern sowas wie:
list.SelectWithIndex((item, index) => ...);
Aber eine Standard-Methode wie SelectWithIndex gab's nie. Klar, mit dem Overload von Select kannst du den Index bekommen, aber… wenn du den Index ohne Transformation oder Projektion nutzen willst, musst du dir mit extra .Select was zusammenbasteln — und das macht den schönen LINQ-Code weniger lesbar.
Wie man vor .NET 9 überlebt hat
Damals hatten C#-Programmierer ihre eigenen Hacks:
var result = list.Select((item, index) => new { item, index });
Oder Kombos mit anderen LINQ-Methoden, aber das war nie so richtig "eingebaut" und nicht so schön, wie man es gern hätte.
2. Neue LINQ-Methode: Index — Was ist das und wofür?
Offizielle Beschreibung
In .NET 9 hat das Entwicklerteam das Gejammer von Millionen Programmierern (okay, Zehntausenden auf Twitter, was auch nicht wenig ist) gehört — und eine neue LINQ-Extension-Methode hinzugefügt: Index. Auf der offiziellen .NET 9 Doku steht dazu:
Index() fügt jedem Element der Sequenz seine laufende Nummer ab null hinzu und gibt Paare (Wert, Index) zurück, ohne dass du explizit anonyme Objekte bauen oder .Select((item, idx) => new { item, idx }) schreiben musst.
Das ist mächtig — jetzt gibt dir Index für jedes Element einfach ein Paar: das Element selbst und seinen Index.
Methoden-Signatur
public static IEnumerable<(T Element, int Index)> Index<T>(this IEnumerable<T> source);
Übersetzt für Menschen: Für jedes Element der Collection bekommst du ein spezielles "Tuple" — Element und seinen Index. Das war's. Keine anonymen Typen mehr bauen.
3. Beispiele für die Nutzung von Index
Ganz einfaches Beispiel
Lass uns eine Liste mit Lieblingsfrüchten nehmen.
var fruits = new List<string> { "Apfel", "Banane", "Orange", "Kiwi" };
foreach (var (fruit, idx) in fruits.Index())
{
Console.WriteLine($"{idx}: {fruit}");
}
Ausgabe:
0: Apfel
1: Banane
2: Orange
3: Kiwi
So einfach! Jetzt kannst du elegant das "Index-Wert"-Paar bekommen und überall in deinem LINQ-Query nutzen.
Integration mit anderen LINQ-Methoden
Index ist ein vollwertiges LINQ-Familienmitglied! Du kannst es easy in deine "Chains" einbauen.
Beispiel 1: Ungerade Elemente nach Index filtern
var numbers = Enumerable.Range(10, 10); // 10, 11, ... 19
var oddIndexes = numbers.Index()
.Where(pair => pair.Index % 2 == 1)
.Select(pair => pair.Element);
Console.WriteLine(string.Join(", ", oddIndexes));
Ausgabe:
11, 13, 15, 17, 19
Beispiel 2: Elemente mit geradem Index modifizieren
var users = new List<string> { "Anna", "Igor", "Katja", "Denis" };
var modified = users.Index()
.Select(pair => pair.Index % 2 == 0 ? pair.Element.ToUpper() : pair.Element.ToLower());
foreach (var name in modified)
Console.WriteLine(name);
Ausgabe:
ANNA
igor
KATJA
denis
Beispiel 3: Mit anderen Collections nach Index zusammenführen (zip-like)
var ids = new[] { 101, 102, 103 };
var names = new[] { "Alice", "Bob", "Charlie" };
var merged = names.Index()
.Join(ids.Index(),
namePair => namePair.Index,
idPair => idPair.Index,
(namePair, idPair) => (idPair.Element, namePair.Element));
foreach (var (id, name) in merged)
Console.WriteLine($"{id}: {name}");
Ausgabe:
101: Alice
102: Bob
103: Charlie
4. Index in einer echten Anwendung
Lass uns in unsere "ewige" Lern-App ein neues Feature einbauen: Wir geben die Liste aller Studenten aus unserer Collection mit ihrer laufenden Nummer aus (zum Beispiel, damit der User einen Studenten per Nummer auswählen kann).
Beispiel: Studentenliste mit Nummern ausgeben
Angenommen, wir haben schon die Klasse Student aus früheren Beispielen:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
Wir bauen eine kleine Studentenliste:
var students = new List<Student>
{
new Student { Name = "Dorothy", Grade = 5 },
new Student { Name = "Peter", Grade = 3 },
new Student { Name = "Finn", Grade = 4 },
new Student { Name = "Helga", Grade = 5 }
};
Jetzt, mit Index, geben wir alle schön mit Nummern aus:
foreach (var (student, idx) in students.Index())
{
Console.WriteLine($"{idx + 1}. {student.Name} — Note: {student.Grade}");
}
Hier
idx + 1 — damit die Nummerierung wie gewohnt bei eins startet, nicht bei null.
Ergebnis:
1. Dorothy — Note: 5
2. Peter — Note: 3
3. Finn — Note: 4
4. Helga — Note: 5
Praktischer Nutzen: Jetzt kann der User einen Studenten per Nummer auswählen — alles ready! Der Code ist simpler, keine "Handzähler" mehr, maximale Lesbarkeit — minimale Bugs.
5. Vergleich: Warum ist Index cooler als die alten Ansätze?
Vor .NET 9: Oldschool-Style
Früher musstest du für Element plus Index den Overload .Select((item, index) => ...) nutzen und anonyme Typen bauen:
var withIndexes = students.Select((student, index) => new { student, index });
Um an die Felder zu kommen, musstest du ständig .student, .index schreiben — und der Typ war anonym, kein schöner benannter Tuple.
Mit .NET 9: 21st Century-Style
Jetzt — keine Sorgen mehr um Felder, anonyme Typen. Alles transparent:
foreach (var (student, idx) in students.Index())
{
// läuft out-of-the-box, intuitiv, einfach, schön
}
Der Code ist cleaner. Weniger Code, weniger Fehler. Perfekt für große LINQ-Chains, wo du keinen Bock auf extra Overloads hast.
6. Feinheiten und Besonderheiten bei der Nutzung
Einsatzbereich
Index funktioniert mit jedem Objekt, das das Interface IEnumerable<T> implementiert. Also — mit allen normalen Collections, Arrays, Ergebnissen anderer LINQ-Queries.
Welchen Typ gibt Index zurück?
Es gibt eine Enumeration von Tuples zurück, wobei das erste Element das Objekt selbst ist (meist Element genannt), das zweite — Index (Typ int). Dank modernem Tuple-Syntax in C# kannst du direkt im Loop foreach (var (element, index) in ...) schreiben und beide Werte direkt in Variablen bekommen.
Kann man es mit Query Syntax nutzen?
Nope, Index ist eine Extension-Methode, query syntax (SQL-ähnliches LINQ) unterstützt sie nicht direkt. Also so geht's nicht:
// Funktioniert nicht!
var query = from s in students.Index() select ...;
Wenn du es kombinieren willst, pack einfach die Methode in Klammern und arbeite mit dem Ergebnis wie mit einer normalen Collection:
var query = from pair in students.Index()
where pair.Index > 1
select pair.Element;
Indexierung: Immer ab null
Index startet immer bei null, wie fast alles in C#-Programmierung. Wenn du bei eins starten willst — einfach an passender Stelle +1 addieren.
8. Fehler und Besonderheiten — Worauf achten?
Viele Studis verwechseln am Anfang Index mit dem Overload .Select((item, index) => ...). Die häufigsten Fehler:
— Sie versuchen, Index in query syntax zu nutzen: "Warum geht das nicht?" — weil es nur als Extension-Methode läuft.
— Sie erwarten, dass der Index bei 1 startet, aber natürlich startet er bei 0.
— Sie denken, dass Index die Original-Collection verändert — aber wie alle LINQ-Methoden gibt sie eine neue Sequenz zurück und ändert das Original nicht (immutable-Ansatz).
Noch ein spannender Punkt: Wenn die Collection "lazy" ist (denk dran: LINQ-Queries sind standardmäßig lazy), dann berechnet Index die Indizes auch erst beim Zugriff auf die Elemente, nicht vorher. Das ist super für große oder sogar unendliche Sequenzen — die Indizierung bleibt immer korrekt und sprengt dir nicht den RAM.
GO TO FULL VERSION