Teilen über


Sammlungsausdrücke – C#-Programmiersprachenreferenz

Verwenden Sie einen Auflistungsausdruck , um allgemeine Sammlungswerte zu erstellen. Ein Auflistungsausdruck ist eine terse Syntax, die Sie vielen verschiedenen Auflistungstypen zuweisen können. Ein Sammlungsausdruck enthält eine Abfolge von Elementen zwischen eckigen Klammern ([ und ]).

Die C#-Sprachreferenz dokumentiert die zuletzt veröffentlichte Version der C#-Sprache. Außerdem enthält sie eine erste Dokumentation zu Funktionen in der öffentlichen Vorschau für die kommende Sprachversion.

In der Dokumentation werden alle Features identifiziert, die in den letzten drei Versionen der Sprache oder in der aktuellen öffentlichen Vorschau eingeführt wurden.

Tipp

Informationen dazu, wann ein Feature erstmals in C# eingeführt wurde, finden Sie im Artikel zum Versionsverlauf der C#-Sprache.

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);
}

Sie können einen Sammlungsausdruck in viele verschiedene Sammlungstypen konvertieren. Im ersten Beispiel wurde veranschaulicht, wie eine Variable mithilfe eines Auflistungsausdrucks 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 Auflistungsausdruck verwenden, in dem eine Kompilierungszeitkonstante erwartet wird, z. B. beim Initialisieren 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

Verwenden Sie ein Spread-Element.. , um Inlineauflistungswerte in einem Auflistungsausdruck zu verwenden. 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. Der Ausdruck in einem Spread-Element muss mithilfe einer foreach Anweisung aufgezählt werden können. Wie im vorherigen Beispiel gezeigt, können Sie Spread-Elemente mit einzelnen Elementen in einem Sammlungsausdruck kombinieren.

Konvertierungen

Sie können einen Sammlungsausdruck in verschiedene Sammlungstypen konvertieren, darunter:

Hinweis

Sie können Sammlungsausdrücke nicht zum Initialisieren von Inlinearrays verwenden. Inlinearrays erfordern unterschiedliche Initialisierungssyntax.

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:

  • Eine bessere Elementkonvertierung wird gegenüber einer besseren Sammlungstypkonvertierung bevorzugt. Mit anderen Worten, der Typ der Elemente im Auflistungsausdruck hat mehr Bedeutung als der Typ der Auflistung. Diese Regeln werden in der Featurespezifikation beschrieben, um eine bessere Konvertierung aus dem Sammlungsausdruck zu ermöglichen.
  • 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 Sie einen Auflistungsausdruck in einen Span oder ReadOnlySpaneine Auflistung konvertieren, stammt der sichere Kontext des Span-Objekts aus dem sicheren Kontext aller Elemente, 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 sich gut verhält. 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.
  • Die Typen im System.Collections.Generic Namespace sind frei von Nebeneffekten. Der Compiler kann Szenarien optimieren, in denen diese Typen möglicherweise als Zwischenwerte verwendet werden, sie werden aber nicht anderweitig verfügbar gemacht.
  • Ein Aufruf eines anwendbaren .AddRange(x) Elements für eine Auflistung führt zu demselben Endwert wie das Durchlaufen x und Hinzufügen aller enumerierten Werte einzeln zu der Auflistung..Add

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

Warnung

Wenn sich ein benutzerdefinierter Sammlungstyp nicht gut verhält, ist das Verhalten nicht definiert, wenn Sie diesen Sammlungstyp mit Auflistungsausdrücken verwenden.

Ihre Typen melden sich für die Unterstützung des Sammlungsausdrucks an, indem sie eine Create() Methode schreiben und das System.Runtime.CompilerServices.CollectionBuilderAttribute Attribut auf den Sammlungstyp anwenden, um die Generatormethode 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;
    private readonly int _count;

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

    public int Count => _count;
    
    public char this[int index]
    {
        get
        {
            if (index >= _count)
                throw new IndexOutOfRangeException();
            return _buffer[index];
        }
    }

    public IEnumerator<char> GetEnumerator()
    {
        for (int i = 0; i < _count; i++)
        {
            yield return _buffer[i];
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() => 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 muss einen endgültigen Parameter des Typs ReadOnlySpan<char>verwenden. Der Typparameter von ReadOnlySpan muss mit dem Elementtyp der Sammlung übereinstimmen. Eine Generatormethode, die eine generische Auflistung zurückgibt, weist das Generische ReadOnlySpan<T> als Parameter auf. Die Methode muss zugänglich und vom Typ static sein.

Ab C# 15 kann die Create Methode zusätzliche Parameter vor dem ReadOnlySpan<T> Parameter haben. Sie können Werte an diese Parameter übergeben, indem Sie ein with(...) Element im Auflistungsausdruck verwenden. Details finden Sie unter Sammlungs-Generator-Argumente .

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.

Auflistungsausdrucksargumente

Ab C# 15 können Sie Argumente an den Konstruktor oder die Factorymethode der zugrunde liegenden Auflistung übergeben, indem Sie ein with(...) Element als erstes Element in einem Auflistungsausdruck verwenden. Mit diesem Feature können Sie Kapazitäts-, Vergleichs- oder andere Konstruktorparameter direkt in der Syntax des Sammlungsausdrucks angeben. Weitere Informationen finden Sie in der Featurespezifikation für Die Argumentargumente des Auflistungsausdrucks.

Das with(...) Element muss das erste Element im Auflistungsausdruck sein. Die im with(...) Element deklarierten Argumente werden an den entsprechenden Konstruktor übergeben oder eine Methode basierend auf dem Zieltyp erstellt. Sie können einen beliebigen gültigen Ausdruck für die Argumente im with Element verwenden.

Konstruktorargumente

Wenn der Zieltyp eine Klasse oder Struktur ist, die implementiert System.Collections.IEnumerablewird, werden die argumente with(...) ausgewertet, und die Ergebnisse werden an den Konstruktor übergeben. Der Compiler verwendet die Überladungsauflösung, um den besten übereinstimmenden Konstruktor auszuwählen:

public void CollectionArgumentsExamples()
{
    string[] values = ["one", "two", "three"];

    // Pass capacity argument to List<T> constructor
    List<string> names = [with(capacity: values.Length * 2), .. values];

    // Pass comparer argument to HashSet<T> constructor
    HashSet<string> set = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO", "hello"];
    // set contains only one element because all strings are equal with OrdinalIgnoreCase

    // Pass capacity to IList<T> (uses List<T> constructor)
    IList<int> numbers = [with(capacity: 100), 1, 2, 3];
}

Im vorherigen Beispiel:

Sammlungs-Generator-Argumente

Bei Typen mit einem System.Runtime.CompilerServices.CollectionBuilderAttributeWerden die im with(...) Element deklarierten Argumente ausgewertet, und die Ergebnisse werden vor dem ReadOnlySpan<T> Parameter an die Create-Methode übergeben. Dieses Feature ermöglicht das Erstellen von Methoden zum Akzeptieren von Konfigurationsparametern:

internal static class MySetBuilder
{
    internal static MySet<T> Create<T>(ReadOnlySpan<T> items) => new MySet<T>(items);
    internal static MySet<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> items) => 
        new MySet<T>(items, comparer);
}

Anschließend können Sie das with(...) Element verwenden, um den Comparer zu übergeben:

public void CollectionBuilderArgumentsExample()
{
    // Pass comparer to a type with CollectionBuilder attribute
    // The comparer argument is passed before the ReadOnlySpan<T> parameter
    MySet<string> mySet = [with(StringComparer.OrdinalIgnoreCase), "A", "a", "B"];
    // mySet contains only two elements: "A" and "B"
}

Die Erstellungsmethode wird mithilfe der Überladungsauflösung basierend auf den bereitgestellten Argumenten ausgewählt. Die ReadOnlySpan<T> enthaltenden Auflistungselemente sind immer der letzte Parameter.

Schnittstellenzieltypen

Mehrere Schnittstellenzieltypen unterstützen Sammlungsausdrucksargumente. In der folgenden Tabelle sind die unterstützten Schnittstellen und deren anwendbare Konstruktorsignaturen aufgeführt:

Schnittstelle Unterstützte with Elemente
IEnumerable<T>, IReadOnlyCollection<T>IReadOnlyList<T> () (nur leer)
ICollection<T>, IList<T> (), (int capacity)

Für IList<T> und ICollection<T>, der Compiler verwendet einen System.Collections.Generic.List<T> mit dem angegebenen Konstruktor.

Einschränkungen

Das with(...) Element hat die folgenden Einschränkungen:

  • Es muss das erste Element im Auflistungsausdruck sein.
  • Argumente können keinen Typ haben dynamic .
  • Es wird für Arrays oder Span-Typen (Span<T>, ReadOnlySpan<T>) nicht unterstützt.