Zum Inhalt
wannacode

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);
}
Ausgabe
Brot
Kaffee
Butter
Jetzt muss jede Schleife über den Warenkorb null ü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);
Ausgabe
Brot, Kaffee, Butter
Artikel: 3
  • List<T> liegt im Namespace System.Collections.Generic. Ein using dafür schreibst du nicht: Neue Projekte fügen es selbst hinzu, wie using System;.
  • Add hängt hinten an, und Remove entfernt und schließt die Lücke: „Kaffee“ rückt auf den Platz von „Milch“.
  • Count ist die Zahl der Elemente gerade jetzt. Es hat keine Klammern, weil es eine Eigenschaft ist. Es ist nicht Length, 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);
Ausgabe
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
  1. cart zeigt auf ein List<string>-Objekt. Darin liegen ein Array _items mit 3 Plätzen und ein Zähler _size.
  2. Add legt jedes Element auf den ersten freien Platz und erhöht _size.
  3. Das Array ist voll. List<T> legt ein neues mit 6 Plätzen an, doppelt so vielen, und kopiert die drei Elemente hinein.
  4. _items zeigt jetzt auf das neue Array, das alte braucht niemand mehr. „Butter“ kommt auf den freien Platz.
  5. Remove entfernt „Milch“, und alles danach rückt einen Platz nach links. Es bleibt keine Lücke.
  6. 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 Capacity sagt dir, wie viele Plätze das Array innen hat. Wird es voll, legt List<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);
Ausgabe
0
4
5 8

Die 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);
Kompilierfehler
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]);
Ausgabe
Brot

In 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]);
Ausgabe
Brot

Das 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"));
Ausgabe
Rechnung März
Kaffee mit dem Team, Rechnung März, Termin bei der Bank, Lagerbericht
3
False
  • tasks[i] liest, und tasks[i] = x ersetzt.
  • Insert(i, x) fügt bei Index i ein und schiebt den Rest nach rechts.
  • IndexOf(x) sucht und gibt den Index oder -1 zurück, und Contains(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, nicht IndexOutOfRangeException.
tasks[3] bei 3 Aufgaben
Unhandled exception. System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
  • ^1 zählt vom Ende: tasks[^1] ist die letzte Aufgabe, „Lagerbericht“.
  • Contains und IndexOf vergleichen mit Equals. 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);
Antwort

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]);
}
Ausgabe
- Brot
- Kaffee
- Butter
1. Brot
2. Kaffee
3. Butter
Genauer, für Neugierige
  • Entferne keine Elemente aus einer Liste, während du sie mit foreach durchläufst. Das Programm bricht ab:
foreach (string item in cart)
{
if (item == "Brot")
{
cart.Remove(item);
}
}
Fehler
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));
Ausgabe
Milch
Im Modul „Modernes C#“ siehst du einen kürzeren Weg: RemoveAll.

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);
Ausgabe
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 zwei object-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);
Ausgabe
False
True
True

Die beiden 127 werden getrennt verpackt, also gibt es zwei Objekte. Equals vergleicht die Werte.

  • Die alte Klasse ArrayList (ohne spitze Klammern, aus System.Collections) hält alles als object. 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);
Ausgabe
2

Darum nimmst du heute List<int>.

  • Ein int kann nicht null sein. Eine Zahl, die fehlen kann, schreibst du als int?:
int? discount = null;
Console.WriteLine(discount.HasValue);
discount = 10;
Console.WriteLine(discount.HasValue + " " + discount);
Ausgabe
False
True 10

Probier 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.

Genauer, für Neugierige
  • Ist der Wert nicht darin, bricht Remove das Programm nicht ab und entfernt nichts. Es gibt false zurück:
List<int> seats = new List<int> { 12, 7, 5 };
Console.WriteLine(seats.Remove(2));
Console.WriteLine(string.Join(", ", seats));
Ausgabe
False
12, 7, 5

Die 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));
Ausgabe
Berlin, Dresden, Hamburg
5, 12, 30

new 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");
Kompilierfehler
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);
Ausgabe
4
Genauer, 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 du StringComparer.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));
Ausgabe
apfel, banane, Birne, Traube
Birne, 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

Löse die Aufgaben in deinem Editor. Die Liste ist ein 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.

Ausgabe
Genommen: Unter dem Joch
2 ü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.

Ausgabe
2026-03-31 Bericht erstes Quartal
2026-05-20 Neuer Kaffeelieferant

3. 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.

Ausgabe
Collections: List<T> wächst von selbst
Gelöscht: OOP
Notizen: 2
Es 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.

Eingabe
4
12
7
Ausgabe
2 + 2 = Richtig!
3 * 4 = Richtig!
15 - 9 = Falsch, die Antwort ist 6.
Ergebnis: 2 von 3

Weitere 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, list und end und halte die Liste zwischen ihnen. Der Befehl, einen Artikel zu entfernen, der nicht darin ist, sagt „Kein solcher Artikel.“

Test

Teste dich

1. Warum ist eine List<T> für einen Warenkorb praktischer als ein Array?
2. Was sagt <string> in List<string> cart?
3. List<int> seats enthält 12, 2, 7, 5. Was bleibt nach seats.Remove(2)?
4. Kannst du List<int> schreiben?
5. IReadOnlyList<string> days = new List<string> { "Mo", "Di" }; Was passiert bei days.Add("Mi")?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·