Listen
Kurz gesagt: Eine Liste ist eine Collection, die von selbst wächst und schrumpft; List<T> hält die Elemente der Reihe nach wie ein Array, und List<string> sagt, welchen Typ sie haben.
Problem
Der Warenkorb ist voll
Du schreibst den Warenkorb eines Onlineshops und hältst die Artikel in einem Array. Aber ein Array hat eine Größe, die sich nicht ändert. Legt der Kunde einen vierten Artikel hinein, legst du ein größeres Array an und kopierst alles. Nimmt er einen Artikel heraus, bleibt eine Lücke:
string[] cart = { "Brot", "Milch", "Kaffee" };
// Ein vierter Artikel: Das Array ist voll, also legen wir ein größeres an und kopieren.string[] bigger = new string[cart.Length + 1];for (int i = 0; i < cart.Length; i++){ bigger[i] = cart[i];}bigger[cart.Length] = "Butter";cart = bigger;
// Der Kunde nimmt die Milch heraus.cart[1] = null;
foreach (string item in cart){ Console.WriteLine(item);}Brot
KaffeeButternull überspringen: Console.WriteLine gibt es als leere Zeile aus. Und die Zahl der Artikel ist nicht mehr cart.Length.Das ist viel Code für etwas, das jeder Shop ständig macht.
Die Idee
List<T>: ein Array, das sich selbst kümmert
C# hat dafür eine fertige Klasse: List<T>. Sie hält die Elemente in einem Array, erledigt das Kopieren und Verschieben aber selbst. So eine Collection heißt Liste:
List<string> cart = new List<string>();cart.Add("Brot");cart.Add("Milch");cart.Add("Kaffee");cart.Add("Butter");cart.Remove("Milch");Console.WriteLine(string.Join(", ", cart));Console.WriteLine("Artikel: " + cart.Count);Brot, Kaffee, ButterArtikel: 3List<T>liegt im NamespaceSystem.Collections.Generic. Einusingdafür schreibst du nicht: Neue Projekte fügen es selbst hinzu, wieusing System;.Addhängt hinten an, undRemoveentfernt und schließt die Lücke: „Kaffee“ rückt auf den Platz von „Milch“.Countist die Zahl der Elemente gerade jetzt. Es hat keine Klammern, weil es eine Eigenschaft ist. Es ist nichtLength, weil eine Liste kein Array ist.Console.WriteLine(cart)gibt nicht die Elemente aus, sondern nur den Namen des Typs:
List<string> cart = new List<string>();cart.Add("Brot");Console.WriteLine(cart);System.Collections.Generic.List`1[System.String]Darum verbindet string.Join(", ", cart) die Elemente zu einem Text, getrennt durch Kommas.
So sieht es in List<T> aus. Die Zahl in den Klammern ist die Anfangsgröße des Arrays. Ohne sie ist das Array leer und bekommt beim ersten Add 4 Plätze. Hier ist es 3, damit du siehst, was passiert, wenn das Array voll wird:
List<string> cart = new List<string>(3);cart.Add("Brot");cart.Add("Milch");cart.Add("Kaffee");cart.Add("Butter");cart.Remove("Milch");Console.WriteLine(string.Join(", ", cart));Console.WriteLine("Artikel: " + cart.Count);Schritt für Schritt, in Worten
- cart zeigt auf ein List<string>-Objekt. Darin liegen ein Array _items mit 3 Plätzen und ein Zähler _size.
- Add legt jedes Element auf den ersten freien Platz und erhöht _size.
- Das Array ist voll. List<T> legt ein neues mit 6 Plätzen an, doppelt so vielen, und kopiert die drei Elemente hinein.
- _items zeigt jetzt auf das neue Array, das alte braucht niemand mehr. „Butter“ kommt auf den freien Platz.
- Remove entfernt „Milch“, und alles danach rückt einen Platz nach links. Es bleibt keine Lücke.
- Von außen siehst du nur eine Liste mit drei Artikeln: Das Kopieren und Verschieben erledigt List<T>. Capacity sagt dir, wie viele Plätze sie hat.
Genauer, für Neugierige
- Die Eigenschaft
Capacitysagt dir, wie viele Plätze das Array innen hat. Wird es voll, legtList<T>ein neues Array mit doppelt so vielen Plätzen an:
List<string> cart = new List<string>();Console.WriteLine(cart.Capacity);cart.Add("Brot");Console.WriteLine(cart.Capacity);for (int i = 0; i < 4; i++){ cart.Add("Milch");}Console.WriteLine(cart.Count + " " + cart.Capacity);045 8Die Idee
Was in die spitzen Klammern gehört
List<string> bedeutet „eine Liste mit Text“. Der Typ in den spitzen Klammern heißt Typparameter (Generics). Er sagt dem Compiler, was in der Liste ist, und der Compiler setzt es durch:
List<string> cart = new List<string>();cart.Add("Brot");cart.Add(5);error CS1503: Argument 1: cannot convert from 'int' to 'string'Darum gibt cart[0] direkt einen string zurück, ohne Umwandlung.
Rechts steht der Typ noch einmal: new List<string>(). Es gibt auch eine Kurzform, new(). Dann nimmt der Compiler den Typ von der linken Seite:
List<string> cart = new();cart.Add("Brot");Console.WriteLine(cart[0]);BrotIn C# hat die Variable meist die Klasse selbst als Typ, List<string>. Die Liste hat auch ein Interface, IList<string>, mit einem I vorne wie jedes Interface in C#. Es sagt, was eine Liste kann, und List<string> ist eine Art, es zu tun:
IList<string> cart = new List<string>();cart.Add("Brot");Console.WriteLine(cart[0]);BrotDas ist die Polymorphie aus dem Modul „Vererbung und Polymorphie“. Das Interface siehst du am häufigsten als Typ eines Parameters.
Die Idee
Indizes: [i], Insert, IndexOf
Eine Liste hat Indizes wie ein Array, von 0 bis Count - 1, und du schreibst sie mit denselben [ ]:
List<string> tasks = new List<string>();tasks.Add("Rechnung März");tasks.Add("Bank anrufen");tasks.Add("Lagerbericht");
Console.WriteLine(tasks[0]);tasks[1] = "Termin bei der Bank";tasks.Insert(0, "Kaffee mit dem Team");Console.WriteLine(string.Join(", ", tasks));Console.WriteLine(tasks.IndexOf("Lagerbericht"));Console.WriteLine(tasks.Contains("Bank anrufen"));Rechnung MärzKaffee mit dem Team, Rechnung März, Termin bei der Bank, Lagerbericht3Falsetasks[i]liest, undtasks[i] = xersetzt.Insert(i, x)fügt bei Indexiein und schiebt den Rest nach rechts.IndexOf(x)sucht und gibt den Index oder-1zurück, undContains(x)sagt nur, ob es darin ist.
Genauer, für Neugierige
- Ein Index außerhalb der Liste bricht das Programm ab. Die Exception ist eine andere als bei einem Array:
ArgumentOutOfRangeException, nichtIndexOutOfRangeException.
Unhandled exception. System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')^1zählt vom Ende:tasks[^1]ist die letzte Aufgabe, „Lagerbericht“.ContainsundIndexOfvergleichen mitEquals. Bei Text heißt das „dieselben Zeichen“, darum finden sie gleichen Text, nicht nur dasselbe Objekt.- Einfügen und Entfernen vorne sind in einer großen Liste langsam: Jedes Element nach dem Index rückt einen Platz weiter. Bei einer Million Elementen sind das eine Million Verschiebungen.
Probier es aus
Wer ist dran?
Die Liste der Patienten, die beim Arzt warten. Jonas geht hinein, und Anna hat einen Termin, darum bekommt sie den zweiten Platz.
Was gibt das Programm aus?
List<string> waiting = new List<string>();waiting.Add("Jonas");waiting.Add("Maria");waiting.Add("Peter");waiting.RemoveAt(0);waiting.Insert(1, "Anna");Console.WriteLine(string.Join(", ", waiting) + " " + waiting.Count);RemoveAt(0) entfernt Jonas, und Maria wird Erste, bei Index 0. Insert(1, "Anna") fügt Anna bei Index 1 ein und schiebt Peter nach rechts. Drei bleiben übrig.
Die Idee
Die Elemente durchlaufen
Eine Liste durchläufst du wie ein Array: mit foreach, wenn du nur die Elemente brauchst, oder mit for und einem Index, wenn du auch die Nummer brauchst:
List<string> cart = new List<string>();cart.Add("Brot");cart.Add("Kaffee");cart.Add("Butter");
foreach (string item in cart){ Console.WriteLine("- " + item);}for (int i = 0; i < cart.Count; i++){ Console.WriteLine((i + 1) + ". " + cart[i]);}- Brot- Kaffee- Butter1. Brot2. Kaffee3. ButterGenauer, für Neugierige
- Entferne keine Elemente aus einer Liste, während du sie mit
foreachdurchläufst. Das Programm bricht ab:
foreach (string item in cart){ if (item == "Brot") { cart.Remove(item); }}Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.Die Exception kommt immer, auch wenn du das vorletzte Element entfernst: Beim nächsten Schritt prüft foreach, ob sich die Liste geändert hat.
- Wenn du entfernen musst, durchläufst du mit einem Index von hinten nach vorne. So verschiebt das Entfernen nicht die Elemente, die du noch nicht geprüft hast:
List<string> cart = new List<string>();cart.Add("Brot");cart.Add("Milch");cart.Add("Brot");for (int i = cart.Count - 1; i >= 0; i--){ if (cart[i] == "Brot") { cart.RemoveAt(i); }}Console.WriteLine(string.Join(", ", cart));MilchRemoveAll.Die Idee
Zahlen in einer Liste: List<int>
Eine Liste mit Noten ist eine List<int>. In die spitzen Klammern passt auch ein primitiver Typ wie int, double oder bool, also liegt die Zahl so in der Liste, wie sie ist:
List<int> scores = new List<int>();scores.Add(85);scores.Add(64);scores.Add(91);
int total = 0;foreach (int score in scores){ total += score;}Console.WriteLine("Durchschnitt: " + (double) total / scores.Count);Durchschnitt: 80(double) macht aus der Summe eine Kommazahl, damit die Division keine Ganzzahldivision ist. C# gibt 80 aus, nicht 80.0: Eine Kommazahl ohne Nachkommateil erscheint ohne Punkt.
Genauer, für Neugierige
- Auch C# hat Boxing. Kommt ein primitiver Typ in eine Variable vom Typ
object, packt C# ihn in ein Objekt. Und==auf zweiobject-Werten prüft, ob sie auf dasselbe Objekt zeigen:
object a = 127;object b = 127;Console.WriteLine(a == b);Console.WriteLine(a.Equals(b));
int c = 127;int d = 127;Console.WriteLine(c == d);FalseTrueTrueDie beiden 127 werden getrennt verpackt, also gibt es zwei Objekte. Equals vergleicht die Werte.
- Die alte Klasse
ArrayList(ohne spitze Klammern, ausSystem.Collections) hält alles alsobject. Sie nimmt eine Zahl und Text an und verpackt jede Zahl:
ArrayList old = new ArrayList();old.Add(85);old.Add("Brot");Console.WriteLine(old.Count);2Darum nimmst du heute List<int>.
- Ein
intkann nichtnullsein. Eine Zahl, die fehlen kann, schreibst du alsint?:
int? discount = null;Console.WriteLine(discount.HasValue);discount = 10;Console.WriteLine(discount.HasValue + " " + discount);FalseTrue 10Probier es aus
Finde den Fehler
Ein Theater hält die belegten Plätze in einer Liste. Ein Kunde gibt Platz 2 zurück, und das Programm soll 12, 7, 5 ausgeben. Stattdessen gibt es 12, 2, 5 aus. In welcher Zeile steckt der Fehler?
Wähle die Zeile mit dem Fehler.
RemoveAt(i) entfernt nach Index: RemoveAt(2) entfernt das Element bei Index 2, also Platz 7. Remove entfernt nach Wert: seats.Remove(2). In C# haben die beiden Methoden verschiedene Namen, also bedeutet Remove(2) immer den Wert 2. Aber bei Zahlen verwechselt man leicht, welche der beiden man braucht, weil Index und Wert beide Zahlen sind.Genauer, für Neugierige
- Ist der Wert nicht darin, bricht
Removedas Programm nicht ab und entfernt nichts. Es gibtfalsezurück:
List<int> seats = new List<int> { 12, 7, 5 };Console.WriteLine(seats.Remove(2));Console.WriteLine(string.Join(", ", seats));False12, 7, 5Die Idee
Sortieren und eine Liste nur zum Lesen
Sort() sortiert die Liste an Ort und Stelle: Text alphabetisch, Zahlen nach Größe.
List<string> cities = new List<string>();cities.Add("Hamburg");cities.Add("Berlin");cities.Add("Dresden");cities.Sort();Console.WriteLine(string.Join(", ", cities));
List<int> prices = new List<int> { 30, 5, 12 };prices.Sort();Console.WriteLine(string.Join(", ", prices));Berlin, Dresden, Hamburg5, 12, 30new List<int> { 30, 5, 12 } erzeugt eine Liste aus den Werten, die du aufzählst. Es ist eine gewöhnliche Liste, und du kannst sie ändern.
Eine Liste, die sich nicht ändert, deklarierst du mit dem Interface IReadOnlyList<T>. Es hat nur die Member, die lesen: den Indexer und Count. Darum lässt dich der Compiler nichts hinzufügen, entfernen oder sortieren:
IReadOnlyList<string> days = new List<string> { "Mo", "Di", "Mi" };days.Add("Do");error CS1061: 'IReadOnlyList<string>' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'IReadOnlyList<string>' could be found (are you missing a using directive or an assembly reference?)IReadOnlyList macht keine Kopie. Es versteckt nur die Member, die die Liste ändern. Hat jemand die List selbst, ist seine Änderung auch über days zu sehen:
List<string> week = new List<string> { "Mo", "Di", "Mi" };IReadOnlyList<string> days = week;week.Add("Do");Console.WriteLine(days.Count);4Genauer, für Neugierige
Sort()sortiert Text nach den Regeln der Sprache, die auf dem Computer eingestellt ist (der Kultur). Groß- und Kleinbuchstaben werden nicht getrennt, darum kommt „apfel“ vor „Birne“. Übergibst duStringComparer.Ordinal, wird Text nach den Codes seiner Zeichen sortiert, und Großbuchstaben kommen vor Kleinbuchstaben:
List<string> fruit = new List<string> { "apfel", "Birne", "banane", "Traube" };fruit.Sort();Console.WriteLine(string.Join(", ", fruit));fruit.Sort(StringComparer.Ordinal);Console.WriteLine(string.Join(", ", fruit));apfel, banane, Birne, TraubeBirne, Traube, apfel, banane- Wie du eigene Objekte sortierst, zum Beispiel Produkte nach Preis, siehst du in der Lektion „Klassen in Klassen“.
Übung
Listen in Klassen
private-Feld der Klasse, und Main ruft nur ihre Methoden auf.1. Ein Bücherregal. Eine Klasse BookShelf mit einer Methode AddBook(string title), die das Buch ans Ende stellt, und TakeBook(), die das erste Buch zurückgibt und entfernt (oder null, wenn das Regal leer ist). Füge eine Eigenschaft Count und ToString() hinzu. Füge in Main „Unter dem Joch“, „Tabak“ und „Der eiserne Leuchter“ hinzu, nimm ein Buch und gib den Rest aus.
Genommen: Unter dem Joch2 übrig: [Tabak, Der eiserne Leuchter]2. Ein Ereignisprotokoll. Eine Klasse Event mit einem Datum und einer Beschreibung und eine Klasse EventLog mit den Methoden AddEvent(string date, string description) und GetEventsBetween(string from, string to), die eine Liste der Ereignisse zwischen den beiden Daten zurückgibt, einschließlich. Die Daten sind Text in der Form 2026-03-31. In dieser Form sortiert sich der Text genauso wie die Daten, darum vergleicht string.CompareOrdinal(a, b) sie richtig. Es gibt eine negative Zahl zurück, wenn a vor b kommt, 0, wenn sie gleich sind, und eine positive Zahl, wenn a nach b kommt. Füge vier Ereignisse hinzu: 2026-01-15 „Jahresinventur“, 2026-03-31 „Bericht erstes Quartal“, 2026-05-20 „Neuer Kaffeelieferant“, 2026-09-01 „Neues Schuljahr“. Gib die vom 2026-03-01 bis 2026-06-30 aus.
2026-03-31 Bericht erstes Quartal2026-05-20 Neuer Kaffeelieferant3. Ein Notizbuch. Eine Klasse Note mit einem Titel und einem Inhalt und eine Klasse Notebook mit den Methoden AddNote, FindNote(string title) (gibt die Notiz oder null zurück), DeleteNote(string title) (gibt true zurück, wenn es etwas gelöscht hat) und einer Eigenschaft Count. Füge die Notizen „OOP“, „Collections“ und „SQL“ hinzu, finde „Collections“, lösche „OOP“ und prüfe, dass sie weg ist.
Collections: List<T> wächst von selbstGelöscht: OOPNotizen: 2Es gibt keine Notiz OOP.4. Kopfrechnen. Eine Klasse Question mit einem Text und der richtigen Antwort und eine Klasse MathGame mit einer Liste von Fragen, einer Methode AddQuestion und einer Methode Start(). Start stellt die Fragen nacheinander, liest die Antwort und sagt am Ende, wie viele richtig waren. Die Fragen sind 2 + 2, 3 * 4 und 15 - 9.
41272 + 2 = Richtig!3 * 4 = Richtig!15 - 9 = Falsch, die Antwort ist 6.Ergebnis: 2 von 3Weitere Aufgaben
- Keine Wiederholungen. Lies 6 Namen ein und sammle sie in einer Liste, wobei du die überspringst, die schon darin sind. Für die Eingabe Jonas, Maria, Jonas, Anna, Maria, Peter ist die Ausgabe
Jonas, Maria, Anna, Peter. - Der teuerste Artikel. Eine List<double> enthält Preise. Finde den höchsten und seinen Index, ohne
Max. - Die Liste umdrehen. Schreib eine Methode, die eine neue Liste mit denselben Elementen in umgekehrter Reihenfolge zurückgibt. Ändere das Original nicht.
- Eine Einkaufsliste. Lies die Befehle
add milk,remove milk,listundendund halte die Liste zwischen ihnen. Der Befehl, einen Artikel zu entfernen, der nicht darin ist, sagt „Kein solcher Artikel.“
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Kapselung · Polymorphie · Interfaces · Exceptions
- Geht weiter in: Stack, Queue und verkettete Liste · Dictionary und HashSet · Hashcodes und Hashfunktionen · Klassen in Klassen
- Aufgaben zum Thema: Collections