Wird geladen...

Kollektionen in C#: Liste, Wörterbuch, Warteschlange, Stapel

Lernen Sie Collections in C# wie List, Dictionary, Queue und Stack kennen, um Daten effizient zu verwalten – mit Beispielen.

In C# werden Kollektionsklassen verwendet, um mehrere Werte dynamisch zu speichern und effizient zu verarbeiten. Arrays haben eine feste Größe, aber Kollektionen können bei Bedarf wachsen oder schrumpfen. In diesem Artikel werden List<T>, Dictionary<TKey,TValue> (und KeyValuePair<,>), SortedList<TKey,TValue>, Queue<T>, Stack<T>, HashSet<T> und LinkedList<T> mit Beispielen behandelt.


List<T>

List<T> ist eine dynamisch vergrößerbare Liste, die Elemente desselben Typs in Reihenfolge speichert. Sie bietet umfangreiche Methoden zum Hinzufügen/Entfernen, Suchen und Sortieren.


using System;
using System.Collections.Generic;
using System.Linq;

var zahlen = new List<int> { 5, 1, 9 };
zahlen.Add(3);                        // am Ende hinzufügen
zahlen.Insert(1, 7);                  // an bestimmtem Index einfügen
zahlen.Remove(9);                     // nach Wert entfernen (erstes Vorkommen)
zahlen.RemoveAll(x => x % 2 == 1);    // alle ungeraden Zahlen entfernen
zahlen.AddRange(new[] { 10, 2, 8 });

zahlen.Sort();                        // aufsteigend sortieren
zahlen.Reverse();                     // Reihenfolge umkehren

Console.WriteLine(string.Join(", ", zahlen));
Console.WriteLine(zahlen.Contains(8)); // true

// LINQ-Filterung
var gerade = zahlen.Where(n => n % 2 == 0).ToList();
Console.WriteLine(string.Join(", ", gerade));

Dictionary<TKey,TValue> und KeyValuePair<TKey,TValue>

Dictionary speichert Schlüssel–Wert-Paare. Schlüssel sind eindeutig. Es ist hash-basiert und bietet sehr schnelle Zugriffe.


using System;
using System.Collections.Generic;

var preise = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase)
{
    ["Apfel"] = 5.75m,
    ["Birne"] = 7.25m
};

// Hinzufügen oder aktualisieren
preise["Banane"] = 12m;  // fügt hinzu, wenn nicht vorhanden, aktualisiert sonst

// Sicheres Lesen
if (preise.TryGetValue("APFEL", out var preis))
    Console.WriteLine($"Apfel: {preis} EUR");

// Schlüsselprüfung
if (!preise.ContainsKey("Kirsche"))
    preise.Add("Kirsche", 18.5m);

// Iteration mit KeyValuePair
foreach (KeyValuePair<string, decimal> kv in preise)
    Console.WriteLine($"{kv.Key} → {kv.Value} EUR");

// Wert aktualisieren
preise["Apfel"] = preise["Apfel"] * 1.10m; // +10% Preis

Tipp: KeyValuePair<TKey,TValue> stellt beim Iterieren .Key und .Value bereit. Zum Aktualisieren wird direkt dict[key] verwendet.


SortedList<TKey,TValue>

SortedList hält Schlüssel aufsteigend sortiert und erlaubt indexbasierten Zugriff über Keys und Values. Sie kombiniert Eigenschaften von Dictionary und Array.


using System;
using System.Collections.Generic;

var preisliste = new SortedList<string, decimal>(StringComparer.OrdinalIgnoreCase)
{
    ["Birne"] = 7.25m,
    ["Apfel"] = 5.75m,
    ["Banane"] = 12m,
    ["Kirsche"] = 18.5m
};

// Sortierte Schlüssel:
Console.WriteLine(string.Join(", ", preisliste.Keys)); // Apfel, Banane, Birne, Kirsche

// Indexbasierter Zugriff:
for (int i = 0; i < preisliste.Count; i++)
    Console.WriteLine($"#{i} {preisliste.Keys[i]} → {preisliste.Values[i]}");

// Benutzerdefinierter Comparer (absteigend):
var absteigend = new SortedList<string, decimal>(Comparer<string>.Create((a,b) => StringComparer.OrdinalIgnoreCase.Compare(b,a)));
absteigend["Apfel"] = 5.75m;
absteigend["Birne"] = 7.25m;
Console.WriteLine(string.Join(", ", absteigend.Keys)); // umgekehrte Reihenfolge
  • SortedList verschiebt beim Einfügen/Entfernen Elemente, um die Reihenfolge zu erhalten. Bei vielen Änderungen lieber SortedDictionary nutzen.
  • Wenn Indexzugriffe auf Bereiche benötigt werden, ist SortedList vorteilhaft.

Queue<T> (FIFO)

Queue arbeitet nach dem First In, First Out-Prinzip; nützlich für Workflows und Warteschlangen.


using System;
using System.Collections.Generic;

var bestellungen = new Queue<string>();
bestellungen.Enqueue("Bestellung#1001");
bestellungen.Enqueue("Bestellung#1002");
bestellungen.Enqueue("Bestellung#1003");

Console.WriteLine(bestellungen.Peek());    // nächste ohne Entfernen: Bestellung#1001
Console.WriteLine(bestellungen.Dequeue()); // entfernt: Bestellung#1001
Console.WriteLine(bestellungen.Count);     // 2

Stack<T> (LIFO)

Stack arbeitet nach dem Last In, First Out-Prinzip; genutzt für Undo, Verlauf usw.


using System;
using System.Collections.Generic;

var schritte = new Stack<string>();
schritte.Push("Schritt-1");
schritte.Push("Schritt-2");
schritte.Push("Schritt-3");

Console.WriteLine(schritte.Peek()); // Schritt-3
Console.WriteLine(schritte.Pop());  // Schritt-3 (entfernt)
Console.WriteLine(schritte.Pop());  // Schritt-2

HashSet<T>

HashSet speichert eine Sammlung eindeutiger Elemente. Doppelte Werte werden ignoriert. Es ist ideal für Mengenoperationen (Vereinigung, Schnittmenge, Differenz).


using System;
using System.Collections.Generic;

var a = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "apfel", "birne", "banane" };
var b = new HashSet<string> { "Banane", "kirsche" };

var vereinigung = new HashSet<string>(a);
vereinigung.UnionWith(b);           // A ∪ B

var schnitt = new HashSet<string>(a);
schnitt.IntersectWith(b);           // A ∩ B

var differenz = new HashSet<string>(a);
differenz.ExceptWith(b);            // A \ B

Console.WriteLine(string.Join(", ", vereinigung)); // apfel, birne, banane, kirsche
Console.WriteLine(string.Join(", ", schnitt));     // banane
Console.WriteLine(string.Join(", ", differenz));   // apfel, birne
Console.WriteLine(a.Add("Apfel"));                 // false (schon vorhanden, case-insensitive)

Tipp: Mitgliedschaftstests (Contains) und Einfügen sind in HashSet meist O(1) amortisiert; ideal zum Entfernen von Duplikaten in großen Datenmengen.


LinkedList<T>

LinkedList ist eine doppelt verkettete Liste. Einfügen/Entfernen an bekannten Knoten kostet O(1). Zufälliger Indexzugriff ist nicht möglich; Traversieren ist erforderlich.


using System;
using System.Collections.Generic;

var liste = new LinkedList<string>();
var node1 = liste.AddFirst("Start");
var node2 = liste.AddLast("Ende");

liste.AddAfter(node1, "Mitte-1");   // Start <-> Mitte-1 <-> Ende
liste.AddBefore(node2, "Mitte-2");  // Start <-> Mitte-1 <-> Mitte-2 <-> Ende

// Knoten entfernen
var mitte1 = node1.Next;
liste.Remove(mitte1);

// Traversieren
for (var n = liste.First; n != null; n = n.Next)
    Console.WriteLine(n.Value);     // Start, Mitte-2, Ende
  • Schnell für Kopf-/Ende-Operationen und knotengenaue Einfügungen/Entfernungen.
  • Nicht geeignet für zufälligen Indexzugriff; dafür List<T> nutzen.

Beispielanwendung: Bestellverarbeitungspipeline

Im folgenden Szenario werden verschiedene Kollektionen kombiniert: Queue für wartende Bestellungen, Dictionary für Lager und Preise, HashSet für eindeutige Kunden, Stack für Undo, SortedList für Preisliste und LinkedList für „zuletzt angesehene Produkte“.


using System;
using System.Collections.Generic;

var wartend = new Queue<string>(new[] { "BEST-1001", "BEST-1002" });
var lager = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
    ["Apfel"] = 100, ["Birne"] = 50, ["Banane"] = 30
};
var preise = new SortedList<string, decimal> { ["Birne"] = 7.25m, ["Apfel"] = 5.75m, ["Banane"] = 12m };
var kunden = new HashSet<string>();        // eindeutige Kunden-IDs
var undo = new Stack<Action>();             // Undo-Stack
var zuletzt = new LinkedList<string>();     // zuletzt angesehene Produkte

void InDenWarenkorb(string produkt, int menge)
{
    if (lager.TryGetValue(produkt, out var s) && s >= menge)
    {
        lager[produkt] = s - menge;
        undo.Push(() => lager[produkt] += menge); // Rückgängig-Aktion
        zuletzt.AddFirst(produkt);
        Console.WriteLine($"{menge} x {produkt} hinzugefügt. Verbleibend: {lager[produkt]}");
    }
    else
    {
        Console.WriteLine($"Nicht genügend Bestand: {produkt}");
    }
}

void BestellungVerarbeiten(string bestellNr, string kundenId)
{
    Console.WriteLine($"Verarbeite: {bestellNr}");
    kunden.Add(kundenId);
    InDenWarenkorb("Apfel", 3);
    InDenWarenkorb("Birne", 2);
}

while (wartend.Count > 0)
{
    var bestellung = wartend.Dequeue();
    BestellungVerarbeiten(bestellung, "KUND-001");
}

Console.WriteLine($"Eindeutige Kunden: {kunden.Count}");
Console.WriteLine($"Preisliste (sortiert): {string.Join(", ", preise.Keys)}");

// Rückgängig machen falls nötig:
if (undo.Count > 0)
{
    var rueck = undo.Pop();
    rueck();
}

Console.WriteLine("Zuletzt angesehene (Top 3):");
int k = 0;
for (var n = zuletzt.First; n != null && k < 3; n = n.Next, k++)
    Console.WriteLine($"- {n.Value}");

Kurz & Knapp

  • List<T>: Dynamische, indexbasierte Liste. Ideal für Zufallszugriff.
  • Dictionary<TKey,TValue>: Schlüssel–Wert-Paare, schnelle Suche. Mit KeyValuePair iterieren.
  • SortedList<TKey,TValue>: Sortierte Schlüssel + Indexzugriff. Gut für Bereichsabfragen.
  • Queue<T> (FIFO): Workflows/Warteschlangen.
  • Stack<T> (LIFO): Undo, Verlauf.
  • HashSet<T>: Eindeutige Elemente; Mengenoperationen (∪, ∩, \).
  • LinkedList<T>: Knotenbasierte Liste; schnelle Kopf-/Ende-Einfügungen/Entfernungen.

Leitfaden: Zufallszugriff → List, schnelle Schlüsselsuche → Dictionary, sortierte Schlüssel + Index → SortedList, Eindeutigkeit → HashSet, FIFO → Queue, LIFO → Stack, schnelle Kopf-/Ende-Operationen → LinkedList.


Ähnliche Artikel