Sammlungen

Die .NET-Runtime stellt viele Sammlungstypen bereit, die Gruppen verwandter Objekte speichern und verwalten. Einige der Sammlungstypen, z. B. System.Array, System.Span<T> und System.Memory<T>, werden in der Sprache C# erkannt. Darüber hinaus werden Schnittstellen wie System.Collections.Generic.IEnumerable<T> in der Sprache zum Aufzählen der Elemente einer Sammlung erkannt.

Sammlungen ermöglichen flexibles Arbeiten mit Objektgruppen. Sie können verschiedene Sammlungen anhand der folgenden Merkmale klassifizieren:

  • Elementzugriff: Jede Sammlung kann aufgezählt werden, um in der angegebenen Reihenfolge auf die einzelnen Elemente zuzugreifen. Einige Sammlungen greifen anhand des Index, die Position des Elements in einer sortierten Sammlung, auf Elemente zu. Das gängigste Beispiel ist System.Collections.Generic.List<T>. Andere Sammlungen greifen auf Elemente anhand des Schlüsselszu, wobei einem einzelnen Schlüssel ein Wert zugeordnet ist. Das gängigste Beispiel ist System.Collections.Generic.Dictionary<TKey,TValue>. Sie wählen zwischen diesen Sammlungstypen basierend darauf, wie Ihre App auf Elemente zugreift.
  • Leistungsprofil: Jede Sammlung verfügt über unterschiedliche Leistungsprofile für Aktionen wie das Hinzufügen eines Elements, das Suchen eines Elements oder das Entfernen eines Elements. Sie können einen Sammlungstyp basierend auf den Vorgängen auswählen, die in Ihrer App am häufigsten verwendet werden.
  • Dynamisches Vergrößern und Verkleinern: Die meisten Sammlungen unterstützen das dynamische Hinzufügen oder Entfernen von Elementen. Insbesondere Array, System.Span<T> und System.Memory<T> tun dies nicht.

Zusätzlich zu diesen Merkmalen stellt die Runtime spezialisierte Sammlungen bereit, die das Hinzufügen oder Entfernen von Elementen oder das Ändern der Elemente der Sammlung verhindern. Andere spezialisierte Sammlungen bieten Sicherheit für gleichzeitigen Zugriff in Multithread-Apps.

Sie finden alle Sammlungstypen in der .NET-API-Referenz. Weitere Informationen finden Sie unter Häufig verwendete Auflistungstypen und Auswählen einer Auflistungsklasse.

Hinweis

Schließen Sie bei den Beispielen in diesem Artikel using-Anweisungen für die System.Collections.Generic- und System.Linq-Namespaces ein.

Arrays werden durch System.Array dargestellt und verfügen über Syntaxunterstützung in der C#-Sprache. Diese Syntax bietet präzisere Deklarationen für Arrayvariablen.

System.Span<T> ist ein ref struct-Typ, der eine Momentaufnahme über eine Abfolge von Elementen bereitstellt, ohne diese Elemente zu kopieren. Der Compiler erzwingt Sicherheitsregeln, um sicherzustellen, dass auf Span nicht zugegriffen werden kann, wenn sich die Sequenz, auf die verwiesen wird, nicht mehr im Bereich befindet. Er wird in vielen .NET-APIs verwendet, um die Leistung zu verbessern. Memory<T> bietet ein ähnliches Verhalten, wenn Sie keinen ref struct-Typ verwenden können.

Ab C# 12 können alle Sammlungstypen mithilfe eines Sammlungsausdrucks initialisiert werden.

Indizierbare Sammlungen

Eine indizierbare Sammlung ist eine Sammlung, in der Sie mithilfe ihres Index auf jedes Element zugreifen können. Der Index ist die Anzahl der Elemente vor ihr in der Sequenz. Daher ist das Element, auf das Index 0 verweist, das erste Element, das Element, auf das Index 1 verweist, das zweite Element usw. In diesen Beispielen wird die List<T>-Klasse verwendet. Dies ist die gängigste indizierbare Sammlung.

Im folgenden Beispiel wird eine Liste mit Zeichenfolgen erstellt und initialisiert, ein Element entfernt und am Ende der Liste ein Element hinzugefügt. Nach jeder Änderung werden die Zeichenfolgen mithilfe einer foreach-Anweisung oder einer for-Schleife durchlaufen:

// Create a list of strings by using a
// collection initializer.
List<string> salmons = ["chinook", "coho", "pink", "sockeye"];

// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook coho pink sockeye

// Remove an element from the list by specifying
// the object.
salmons.Remove("coho");


// Iterate using the index:
for (var index = 0; index < salmons.Count; index++)
{
    Console.Write(salmons[index] + " ");
}
// Output: chinook pink sockeye

// Add the removed element
salmons.Add("coho");
// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook pink sockeye coho

Im folgenden Beispiel werden Elemente aus einer Liste anhand des Index entfernt. Anstelle einer foreach-Anweisung wird eine for-Anweisung verwendet, die die Elemente in absteigender Reihenfolge durchläuft. Die RemoveAt-Methode führt dazu, dass Elemente nach einem entfernten Element einen niedrigeren Indexwert haben.

List<int> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

// Remove odd numbers.
for (var index = numbers.Count - 1; index >= 0; index--)
{
    if (numbers[index] % 2 == 1)
    {
        // Remove the element by specifying
        // the zero-based index in the list.
        numbers.RemoveAt(index);
    }
}

// Iterate through the list.
// A lambda expression is placed in the ForEach method
// of the List(T) object.
numbers.ForEach(
    number => Console.Write(number + " "));
// Output: 0 2 4 6 8

Für den Typ der Elemente in List<T> können Sie auch eine eigene Klasse definieren. Im folgenden Beispiel wird die Galaxy-Klasse, die von List<T> verwendet wird, im Code definiert.

private static void IterateThroughList()
{
    var theGalaxies = new List<Galaxy>
    {
        new (){ Name="Tadpole", MegaLightYears=400},
        new (){ Name="Pinwheel", MegaLightYears=25},
        new (){ Name="Milky Way", MegaLightYears=0},
        new (){ Name="Andromeda", MegaLightYears=3}
    };

    foreach (Galaxy theGalaxy in theGalaxies)
    {
        Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears);
    }

    // Output:
    //  Tadpole  400
    //  Pinwheel  25
    //  Milky Way  0
    //  Andromeda  3
}

public class Galaxy
{
    public string Name { get; set; }
    public int MegaLightYears { get; set; }
}

Schlüssel-Wert-Paar-Sammlungen

In diesen Beispielen wird die Dictionary<TKey,TValue>-Klasse verwendet. Dies ist die gängigste Wörterbuchsammlung. Eine Wörterbuchsammlung ermöglicht es Ihnen, unter Verwendung des Schlüssels der einzelnen Elemente auf die Elemente in der Sammlung zuzugreifen. Jede Hinzufügung zum Wörterbuch besteht aus einem Wert und dem zugeordneten Schlüssel.

Das folgende Beispiel erstellt eine Dictionary-Auflistung und durchläuft das Wörterbuch unter Verwendung einer foreach-Anweisung.

private static void IterateThruDictionary()
{
    Dictionary<string, Element> elements = BuildDictionary();

    foreach (KeyValuePair<string, Element> kvp in elements)
    {
        Element theElement = kvp.Value;

        Console.WriteLine("key: " + kvp.Key);
        Console.WriteLine("values: " + theElement.Symbol + " " +
            theElement.Name + " " + theElement.AtomicNumber);
    }
}

public class Element
{
    public required string Symbol { get; init; }
    public required string Name { get; init; }
    public required int AtomicNumber { get; init; }
}

private static Dictionary<string, Element> BuildDictionary() =>
    new ()
    {
        {"K",
            new (){ Symbol="K", Name="Potassium", AtomicNumber=19}},
        {"Ca",
            new (){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        {"Sc",
            new (){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        {"Ti",
            new (){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };

Im folgenden Beispiel werden die ContainsKey-Methode und die Item[]-Eigenschaft von Dictionary verwendet, um anhand des Schlüssels schnell nach einem Element zu suchen. Die Item-Eigenschaft ermöglicht den Zugriff auf ein Element in der elements-Auflistung unter Verwendung des elements[symbol]-Codes in C#.

if (elements.ContainsKey(symbol) == false)
{
    Console.WriteLine(symbol + " not found");
}
else
{
    Element theElement = elements[symbol];
    Console.WriteLine("found: " + theElement.Name);
}

Im folgenden Beispiel wird stattdessen die TryGetValue-Methode verwendet, um anhand des Schlüssels schnell nach einem Element zu suchen.

if (elements.TryGetValue(symbol, out Element? theElement) == false)
    Console.WriteLine(symbol + " not found");
else
    Console.WriteLine("found: " + theElement.Name);

Iterators

Ein Iterator wird verwendet, um eine benutzerdefinierte Iteration durch eine Auflistung auszuführen. Ein Iterator kann eine Methode oder ein get-Accessor sein. Ein Iterator verwendet eine yield return-Anweisung, um jedes Element der Auflistung separat zurückzugeben.

Sie rufen einen Iterator mithilfe einer foreach-Anweisung auf. Jede Iteration der foreach-Schleife ruft den Iterator auf. Wenn eine yield return-Anweisung im Iterator erreicht ist, wird ein Ausdruck zurückgegeben, und die aktuelle Position im Code wird beibehalten. Wenn der Iterator das nächste Mal aufgerufen wird, wird die Ausführung von dieser Position neu gestartet.

Weitere Informationen finden Sie unter Iteratoren (C#).

Im folgenden Beispiel wird eine Iteratormethode verwendet. Die Iteratormethode verfügt über eine yield return-Anweisung, die sich innerhalb einer for-Schleife befindet. In der ListEvenNumbers-Methode erstellt jede Iteration des foreach-Anweisungstexts einen Aufruf der Iteratormethode, der zur nächsten yield return-Anweisung übergeht.

private static void ListEvenNumbers()
{
    foreach (int number in EvenSequence(5, 18))
    {
        Console.Write(number.ToString() + " ");
    }
    Console.WriteLine();
    // Output: 6 8 10 12 14 16 18
}

private static IEnumerable<int> EvenSequence(
    int firstNumber, int lastNumber)
{
    // Yield even numbers in the range.
    for (var number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

LINQ und Sammlungen

LINQ (Language Integrated Query) kann verwendet werden, um auf Sammlungen zuzugreifen. LINQ-Abfragen stellen Filter-, Sortier- und Gruppierungsfunktionen bereit. Weitere Informationen finden Sie unter Erste Schritte mit LINQ in C#.

Im folgenden Beispiel wird eine LINQ-Abfrage für eine generische List ausgeführt. Die LINQ-Abfrage gibt eine andere Auflistung zurück, die die Ergebnisse enthält.

private static void ShowLINQ()
{
    List<Element> elements = BuildList();

    // LINQ Query.
    var subset = from theElement in elements
                 where theElement.AtomicNumber < 22
                 orderby theElement.Name
                 select theElement;

    foreach (Element theElement in subset)
    {
        Console.WriteLine(theElement.Name + " " + theElement.AtomicNumber);
    }

    // Output:
    //  Calcium 20
    //  Potassium 19
    //  Scandium 21
}

private static List<Element> BuildList() => new()
    {
        { new(){ Symbol="K", Name="Potassium", AtomicNumber=19}},
        { new(){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        { new(){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        { new(){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };