Sammlungsausdrücke – C#-Programmiersprachenreferenz

Sie können einen Sammlungsausdruck verwenden, um allgemeine Sammlungswerte zu erstellen. Ein Sammlungsausdruck ist eine nicht ausführliche Syntax, die bei der Auswertung vielen verschiedenen Sammlungstypen zugewiesen werden kann. Ein Sammlungsausdruck enthält eine Abfolge von Elementen zwischen eckigen Klammern ([ und ]). Im folgenden Beispiel wird eine System.Span<T>-Struktur von string-Elementen deklariert und als Wochentage initialisiert:

Span<string> weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
foreach (var day in weekDays)
{
    Console.WriteLine(day);
}

Ein Sammlungsausdruck kann in viele verschiedene Sammlungstypen konvertiert werden. Im ersten Beispiel wurde veranschaulicht, wie eine Variable mithilfe eines Sammlungsausdrucks initialisiert wird. Der folgende Code zeigt viele andere Einsatzmöglichkeiten für einen Sammlungsausdruck:

// Initialize private field:
private static readonly ImmutableArray<string> _months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

// property with expression body:
public IEnumerable<int> MaxDays =>
    [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

public int Sum(IEnumerable<int> values) =>
    values.Sum();

public void Example()
{
    // As a parameter:
    int sum = Sum([1, 2, 3, 4, 5]);
}

Sie können keinen Sammlungsausdruck verwenden, in dem eine Kompilierzeitkonstante erwartet wird, z. B. bei der Initialisierung einer Konstante oder als Standardwert für ein Methodenargument.

In den beiden vorherigen Beispielen werden Konstanten als Elemente eines Sammlungsausdrucks verwendet. Sie können auch Variablen für die Elemente verwenden, wie im folgenden Beispiel gezeigt:

string hydrogen = "H";
string helium = "He";
string lithium = "Li";
string beryllium = "Be";
string boron = "B";
string carbon = "C";
string nitrogen = "N";
string oxygen = "O";
string fluorine = "F";
string neon = "Ne";
string[] elements = [hydrogen, helium, lithium, beryllium, boron, carbon, nitrogen, oxygen, fluorine, neon];
foreach (var element in elements)
{
    Console.WriteLine(element);
}

Spread-Element

Sie verwenden das Spread-Element .., um Sammlungswerte in einem Sammlungsausdruck inline festzulegen. Im folgenden Beispiel wird eine Sammlung für das vollständige Alphabet erstellt, indem eine Sammlung der Vokale, eine Sammlung der Konsonanten und der Buchstabe „y“ kombiniert werden, der beides sein kann:

string[] vowels = ["a", "e", "i", "o", "u"];
string[] consonants = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
                       "n", "p", "q", "r", "s", "t", "v", "w", "x", "z"];
string[] alphabet = [.. vowels, .. consonants, "y"];

Das Spread-Element ..vowels erzeugt bei der Auswertung fünf Elemente: "a", "e", "i", "o" und "u". Das Spread-Element ..consonants erzeugt 20 Elemente, die Anzahl im Array consonants. Die Variable in einem Spread-Element muss mit einer foreach-Anweisung aufzählbar sein. Wie im vorherigen Beispiel gezeigt, können Sie Spread-Elemente mit einzelnen Elementen in einem Sammlungsausdruck kombinieren.

Konvertierungen

Ein Sammlungsausdruck kann in viele verschiedene Sammlungstypen konvertiert werden, u. a.:

Wichtig

Ein Sammlungsausdruck erstellt immer eine Sammlung, die alle Elemente im Sammlungsausdruck enthält, unabhängig vom Zieltyp der Konvertierung. Wenn beispielsweise das Ziel der Konvertierung System.Collections.Generic.IEnumerable<T> ist, wertet der generierte Code den Sammlungsausdruck aus und speichert die Ergebnisse in einer Speichersammlung.

Dieses Verhalten unterscheidet sich von LINQ, wo eine Sequenz möglicherweise nicht instanziiert wird, bis sie aufgezählt wird. Sie können keine Sammlungsausdrücke verwenden, um eine unendliche Sequenz zu generieren, die nicht aufgezählt wird.

Der Compiler verwendet statische Analysen, um die leistungsstärkste Methode zum Erstellen der Auflistung zu bestimmen, die mit einem Auflistungsausdruck deklariert wurde. Beispielsweise kann der leere Auflistungsausdruck [] als Array.Empty<T>() realisiert werden, wenn das Ziel nach der Initialisierung nicht geändert wird. Wenn das Ziel ein System.Span<T> oder System.ReadOnlySpan<T> ist, kann der Speicher möglicherweise stapelweise zugewiesen werden. Die Featurespezifikation für Auflistungsausdrücke gibt die Regeln an, die der Compiler einhalten muss.

Viele APIs werden mit mehreren Sammlungstypen als Parameter überladen. Da ein Sammlungsausdruck in viele verschiedene Ausdruckstypen konvertiert werden kann, erfordern diese APIs möglicherweise Umwandlungen in den Sammlungsausdruck, um die richtige Konvertierung anzugeben. Die folgenden Konvertierungsregeln beheben einige der Mehrdeutigkeiten:

  • Die Konvertierung in Span<T>, ReadOnlySpan<T> oder einen anderen ref struct-Typ ist besser als eine Konvertierung in einen Typ, der kein ref struct-Typ ist.
  • Die Konvertierung in einen noninterface-Typ ist besser als eine Konvertierung in einen interface-Typ.

Wenn ein Sammlungsausdruck in Span oder ReadOnlySpan konvertiert wird, wird der sichere Kontext des Span-Objekts aus dem sicheren Kontext aller Elemente übernommen, die in der Spanne enthalten sind. Ausführliche Regeln finden Sie in der Spezifikation zu Sammlungsausdrücken.

Sammlungsgenerator

Sammlungsausdrücke funktionieren mit jedem Auflistungstyp, der gut konzipiert ist. Eine gut konzipierte Sammlung weist folgende Merkmale auf:

  • Der Wert von Count oder Length für eine zählende Auflistung erzeugt denselben Wert wie die Anzahl der Elemente, wenn sie aufgezählt werden.
  • Es wird angenommen, dass die Typen im System.Collections.Generic-Namespace keine Nebeneffekte aufweisen. Daher kann der Compiler Szenarien optimieren, in denen solche Typen als Zwischenwerte verwendet werden, andernfalls aber nicht offengelegt werden.
  • Ein Aufruf einiger anwendbarer .AddRange(x)-Member für eine Sammlung führt zu demselben Endwert wie eine Iteration über x und das einzelne Hinzufügen aller enumerierten Werte zur Sammlung mit .Add.

Alle Sammlungstypen in der .NET-Laufzeit sind gut konzipiert.

Warnung

Wenn sich ein benutzerdefinierter Sammlungstyp nicht gut konzipiert ist, ist das Verhalten bei Verwendung dieses Sammlungstyps mit Sammlungsausdrücken nicht definiert.

Ihre Typen aktivieren die Unterstützung von Sammlungsausdrücken, indem eine Create()-Methode geschrieben und System.Runtime.CompilerServices.CollectionBuilderAttribute auf den Sammlungstyp angewendet wird, um die builder-Methode anzugeben. Betrachten Sie beispielsweise eine Anwendung, die Puffer mit einer festen Länge von 80 Zeichen verwendet. Diese Klasse würde etwa wie der folgende Code aussehen:

public class LineBuffer : IEnumerable<char>
{
    private readonly char[] _buffer = new char[80];

    public LineBuffer(ReadOnlySpan<char> buffer)
    {
        int number = (_buffer.Length < buffer.Length) ? _buffer.Length : buffer.Length;
        for (int i = 0; i < number; i++)
        {
            _buffer[i] = buffer[i];
        }
    }

    public IEnumerator<char> GetEnumerator() => _buffer.AsEnumerable<char>().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _buffer.GetEnumerator();

    // etc
}

Sie möchten sie mit Sammlungsausdrücken verwenden, wie im folgenden Beispiel gezeigt:

LineBuffer line = ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'];

Der Typ LineBuffer implementiert das IEnumerable<char>-Element, sodass der Compiler es als Sammlung von char-Elementen erkennt. Der Typparameter der implementierten System.Collections.Generic.IEnumerable<T>-Schnittstelle gibt den Elementtyp an. Sie müssen Ihrer Anwendung zwei Ergänzungen hinzufügen, um einem LineBuffer-Objekt Sammlungsausdrücke zuzuweisen. Zunächst müssen Sie eine Klasse erstellen, die eine Create-Methode enthält:

internal static class LineBufferBuilder
{
    internal static LineBuffer Create(ReadOnlySpan<char> values) => new LineBuffer(values);
}

Die Create-Methode muss ein LineBuffer-Objekt zurückgeben und einen einzelnen Parameter des Typs ReadOnlySpan<char> verwenden. Der Typparameter von ReadOnlySpan muss mit dem Elementtyp der Sammlung übereinstimmen. Eine builder-Methode, die eine generische Sammlung zurückgibt, hätte das generische ReadOnlySpan<T>-Element als Parameter. Die Methode muss zugänglich und vom Typ static sein.

Zum Schluss müssen Sie CollectionBuilderAttribute zur LineBuffer-Klassendeklaration hinzufügen:

[CollectionBuilder(typeof(LineBufferBuilder), "Create")]

Der erste Parameter stellt den Namen der Klasse Builder bereit. Das zweite Attribut stellt den Namen der builder-Methode bereit.