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 §12.8.25 Collection-Ausdrucksklausel des C#-Standards gibt die Regeln an, die der Compiler befolgen 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 besseren Konvertierung aus der Ausdrucksklausel der C#-Sprachspezifikation beschrieben.
  • 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 unter §9.7.2 Ref safe contexts in the C# standard.

Sammlungsgenerator

Sammlungsausdrücke funktionieren mit jedem Auflistungstyp, der sich gut verhält. Eine gut konzipierte Sammlung weist folgende Merkmale auf:

  • Der Wert einer CountLengthzählbaren Auflistung erzeugt denselben Wert wie die Anzahl der Elemente, wenn Sie die Auflistung aufzählen.
  • Die Typen im System.Collections.Generic Namespace sind frei von Nebeneffekten. Der Compiler optimiert Szenarien, in denen diese Typen möglicherweise als Zwischenwerte verwendet werden, aber sie werden andernfalls nicht 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 wechseln zum entsprechenden Konstruktor, oder erstellen Sie eine Methode basierend auf dem Zieltyp. Sie können einen beliebigen gültigen Ausdruck für die Argumente im with Element verwenden.

Argumentausdrücke und Elementausdrücke werden einmal in der von links nach rechts angeordneten Reihenfolge ausgewertet, wobei die with(...) Argumente vor den Auflistungselementen ausgewertet werden. Für eine Spread wie .. GetItems()z. B. Auswertungsaufrufe GetItems() , um die Sammlung zu verbreiten. Aufzählung ist der separate Schritt, in dem Elemente aus dieser Auflistung gelesen und dem Ergebnis hinzugefügt werden. Der Compiler wertet GetItems() an der Position von links nach rechts aus, kann jedoch die Aufzählung verzögern, bis er spätere Elementausdrücke auswertet. Verlassen Sie sich nicht auf Nebenwirkungen von Enumerationen, die vor diesen späteren Ausdrücken auftreten.

Konstruktorargumente

Wenn der Zieltyp eine Klasse oder Struktur ist, die implementiert System.Collections.IEnumerablewird, werden die argumente with(...) ausgewertet, und die Ergebnisse gehen zum Konstruktor. 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:

Für einen Zieltyp, der auf diese Weise erstellt wurde, ist erforderlich, with(...) wenn der Typ keinen barrierefreien parameterlosen Konstruktor aufweist. Verwenden Sie sie, um die Argumente einem barrierefreien Konstruktor zur Verfügung zu stellen. Erfordert z. B NamedValues . einen Namen und erbt das Sammlungsverhalten von List<int>:

public class NamedValues(string name) : List<int>
{
    public string Name { get; } = name;
}

Dieser Sammlungsausdruck liefert den erforderlichen Namen und fügt dann die Elemente hinzu:

NamedValues readings = [with("temperature"), 18, 21, 24];
Console.WriteLine($"{readings.Name}: {string.Join(", ", readings)}");
// output: temperature: 18, 21, 24

Sammlungs-Generator-Argumente

Bei Typen mit einem System.Runtime.CompilerServices.CollectionBuilderAttributeWertewert wertet das with(...) Element die deklarierten Argumente aus und sendet die Ergebnisse vor dem ReadOnlySpan<T> Parameter an die Create-Methode. 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);
}

Verwenden Sie das with(...) Element, 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 Überladungsauflösung wählt die Erstellungsmethode basierend auf den von Ihnen bereitgestellten Argumenten aus. 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.
  • Die argumente in with(...) don't affect whether a conversion exists from the collection expression to a candidate target type. Überladungsauflösung und Typinschluss für einen eingeschlossenen Aufruf ignorieren diese Argumente. Nur das Vorhandensein eines with(...) Elements, nicht dessen Argumente, wirkt sich darauf aus, ob diese Konvertierung vorhanden ist. Nachdem der Zieltyp bestimmt wurde, nehmen die Argumente an der Überladungsauflösung für die Konstruktor- oder Sammlungs-Generator-Methode teil.

Ein Vergleichsargument weist beispielsweise dem Compiler nicht an, eine HashSet<string> Überladung über eine List<string> Überladung auszuwählen:

class Examples
{
    static void Print(List<string> values) { }
    static void Print(HashSet<string> values) { }

    static void Example()
    {
        Print([with(StringComparer.OrdinalIgnoreCase), "one", "ONE"]);
    }
}

Der Aufruf erzeugt CS0121: "Der Aufruf ist mehrdeutig zwischen den folgenden Methoden oder Eigenschaften." Obwohl nur HashSet<string> ein Konstruktor vorhanden ist, der diesen Vergleich akzeptiert, ignoriert der Compiler die with(...) Argumente, wenn er die Print Überladung auswähnt. Weisen Sie dem Auflistungsausdruck zuerst eine HashSet<string> Variable zu, um diese Überladung auszuwählen.

with(...) Ebenso geben Argumente keine Elementtypen für generische Typzuleitungen an:

class Examples
{
    static void Use<T>(List<T> values) { }

    static void Example()
    {
        Use([with(10)]);
    }
}

Dieser Aufruf erzeugt CS0411: "Die Typargumente können nicht von der Verwendung abgeleitet werden. Versuchen Sie, explizit die Typargumente anzugeben." Das Kapazitätsargument bestimmt Tnicht, und die Auflistung enthält keine Elemente, aus denen sie abgeleitet werden soll. Geben Sie das Typargument an, z Use<int>([with(10)]). B. .