Freigeben über


Sammlungsausdrücke

Anmerkung

Dieser Artikel ist eine Featurespezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.

Es kann einige Abweichungen zwischen der Featurespezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.

Weitere Informationen zum Einführen von Featurespezifikationen in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

Champion Issue: https://github.com/dotnet/csharplang/issues/8652

Zusammenfassung

Sammlungsausdrücke führen eine neue, knappe Syntax ein, [e1, e2, e3, etc], um gemeinsame Sammlungswerte zu erstellen. Das Eingliedern anderer Auflistungen in diese Werte ist mit einem Spread-Element möglich..e so: [e1, ..c2, e2, ..c2].

Mehrere sammlungsähnliche Typen können erstellt werden, ohne dass externe BCL-Unterstützung erforderlich ist. Diese Typen sind:

Eine weitere Unterstützung ist für sammlungsähnliche Typen vorhanden, die nicht unter das oben genannte durch ein neues Attribut- und API-Muster fallen, das direkt auf den Typ selbst übernommen werden kann.

Motivation

  • Sammlungsähnliche Werte sind in Programmierung, Algorithmen und insbesondere im C#/.NET-Ökosystem sehr präsent. Fast alle Programme verwenden diese Werte, um Daten zu speichern und Daten von anderen Komponenten zu senden oder zu empfangen. Derzeit müssen fast alle C#-Programme viele verschiedene und unglücklicherweise umfangreiche Ansätze verwenden, um Instanzen solcher Werte zu erzeugen. Einige Ansätze haben auch Leistungseinbußen. Nachfolgend finden Sie einige gängige Beispiele:

    • Arrays, die entweder new Type[] oder new[] vor den { ... } Werten erfordern.
    • Spans, die stackalloc und andere umständliche Konstrukte verwenden können.
    • Sammlungsinitialisierer, die eine Syntax wie new List<T> (ohne Inferenz eines möglicherweise ausführlichen T) vor ihren Werten erfordern und die mehrfache Neuzuweisungen von Speicher verursachen können, weil sie N .Add-Aufrufe verwenden, ohne eine Anfangskapazität zu liefern.
    • Unveränderliche Auflistungen, die Syntax wie ImmutableArray.Create(...) zum Initialisieren der Werte erfordern und zu zwischengeschalteten Zuordnungen und Datenkopien führen können. Effizientere Bauformen (wie ImmutableArray.CreateBuilder) sind umständlich und erzeugen immer noch unvermeidbaren Abfall.
  • Wenn wir das umgebende Ökosystem betrachten, finden wir überall Beispiele dafür, dass die Erstellung von Listen bequemer und angenehmer ist. TypeScript, Dart, Swift, Elm, Python und andere bevorzugen eine prägnante Syntax zu diesem Zweck, mit weit verbreiteter Nutzung und großer Wirkung. Oberflächliche Untersuchungen haben keine wesentlichen Probleme in diesen Ökosystemen ergeben, die durch den Einbau dieser Literale entstehen.

  • C# hat in C# 11 auch Listenmuster hinzugefügt. Dieses Muster ermöglicht das Abgleichen und Deconieren von listenähnlichen Werten mithilfe einer sauberen und intuitiven Syntax. Im Gegensatz zu fast allen anderen Musterkonstrukten fehlt diese Zuordnungs-/Dekonstruktionssyntax jedoch an der entsprechenden Konstruktionssyntax.

  • Das Erzielen der besten Leistung beim Erstellen der einzelnen Sammlungstypen kann schwierig sein. Einfache Lösungen verschwenden häufig sowohl CPU als auch Arbeitsspeicher. Eine wörtliche Form ermöglicht maximale Flexibilität von der Compiler-Implementierung, um das Literal zu optimieren, um ein mindestens so gutes Ergebnis zu erzielen, wie es ein Benutzer bereitstellen könnte, aber mit einfachem Code. Sehr oft wird der Compiler in der Lage sein, besser zu tun, und die Spezifikation zielt darauf ab, die Implementierung große Mengen an Spielraum in Bezug auf die Implementierungsstrategie zu ermöglichen, um dies zu gewährleisten.

Für C# ist eine inklusive Lösung erforderlich. Es sollte die überwiegende Mehrheit der Kunden in Bezug auf die kollektionsähnlichen Arten und Werte erfüllen, die sie bereits haben. Es sollte sich auch in der Sprache natürlich anfühlen und die Leistung beim Musterabgleich widerspiegeln.

Dies führt zu einer natürlichen Schlussfolgerung, dass die Syntax wie [e1, e2, e3, e-etc] oder [e1, ..c2, e2]sein sollte, die den Musteräquivalenten von [p1, p2, p3, p-etc] und [p1, ..p2, p3]entsprechen.

Detailentwurf

Die folgenden Grammatik-Produktionen werden hinzugefügt:

primary_no_array_creation_expression
  ...
+ | collection_expression
  ;

+ collection_expression
  : '[' ']'
  | '[' collection_element ( ',' collection_element )* ']'
  ;

+ collection_element
  : expression_element
  | spread_element
  ;

+ expression_element
  : expression
  ;

+ spread_element
  : '..' expression
  ;

Sammlungsliterale sind zieltypisiert.

Spezifikationsabklärungen

  • Der Kürze halber wird collection_expression in den folgenden Abschnitten als "wörtlich" bezeichnet.

  • expression_element Instanzen werden üblicherweise als e1, e_n, usw. bezeichnet.

  • spread_element Instanzen werden üblicherweise als ..s1, ..s_n usw. bezeichnet.

  • span type bedeutet entweder Span<T> oder ReadOnlySpan<T>.

  • Literale werden häufig als [e1, ..s1, e2, ..s2, etc] angezeigt, um eine Anzahl beliebiger Elemente in beliebiger Reihenfolge darzustellen. Wichtig ist, dass dieses Formular verwendet wird, um alle Fälle darzustellen, z. B.:

    • Leere Literale []
    • Literale, die kein expression_element enthalten.
    • Literale, die kein spread_element enthalten.
    • Literale mit beliebiger Reihenfolge eines beliebigen Elementtyps.
  • Der Iterationstyp von ..s_n ist der Typ der Iterationsvariablen , der so bestimmt wird, als ob s_n als der Ausdruck verwendet würde, über den in einem foreach_statementiteriert wird.

  • Variablen, die mit __name beginnen, werden verwendet, um die Ergebnisse der Auswertung von namedarzustellen, die an einem Speicherort gespeichert sind, sodass sie nur einmal ausgewertet wird. Beispielsweise ist __e1 die Auswertung von e1.

  • List<T>, IEnumerable<T>usw. beziehen sich auf die jeweiligen Typen im System.Collections.Generic Namespace.

  • Die Spezifikation definiert eine Übersetzung des Literals in bestehende C#-Konstrukte. Ähnlich wie bei der Übersetzung von Abfrageausdrückenist das Literal selbst nur dann legal, wenn die Übersetzung zu legalem Code führen würde. Zweck dieser Regel besteht darin, zu vermeiden, dass andere Regeln der Sprache wiederholt werden müssen, die impliziert werden (z. B. zur Konvertierungsfähigkeit von Ausdrücken, wenn sie Speicherorten zugewiesen sind).

  • Eine Implementierung ist nicht erforderlich, um Literale genau wie unten angegeben zu übersetzen. Jede Übersetzung ist legal, wenn dasselbe Ergebnis erzeugt wird und es keine feststellbaren Unterschiede in der Produktion des Ergebnisses gibt.

    • Beispielsweise könnte eine Implementierung Literale wie [1, 2, 3] direkt in einen new int[] { 1, 2, 3 } Ausdruck übersetzen, der die Rohdaten direkt in die Assembly einbettet und damit die Notwendigkeit für __index oder eine Reihe von Anweisungen zum Zuweisen jedes Werts überflüssig macht. Wichtig ist dabei, dass wenn ein beliebiger Schritt der Übersetzung zur Laufzeit eine Ausnahme verursachen kann, der Programmstatus trotzdem im durch die Übersetzung angegebenen Zustand bleibt.
  • Verweise auf "Stack-Zuweisung" beziehen sich auf jede Strategie zur Zuweisung auf dem Stack und nicht auf dem Heap. Wichtig ist, dass es nicht impliziert oder erfordert, dass diese Strategie durch den tatsächlichen stackalloc Mechanismus erfolgt. Zum Beispiel ist die Verwendung von Inline-Arrays ebenfalls ein zulässiger und wünschenswerter Ansatz, um die Stack-Allokation zu erreichen, sofern verfügbar. Beachten Sie, dass in C# 12 Inlinearrays nicht mit einem Auflistungsausdruck initialisiert werden können. Das bleibt ein offener Vorschlag.

  • Bei Sammlungen wird davon ausgegangen, dass sie sich gut verhalten. Zum Beispiel:

    • Es wird davon ausgegangen, dass der Wert von Count für eine Auflistung denselben Wert wie die Anzahl der Elemente erzeugt, wenn sie aufgezählt werden.
    • Die in dieser Spezifikation verwendeten Typen, die im System.Collections.Generic-Namespace definiert sind, werden als nebeneffektfrei angenommen. Daher kann der Compiler Szenarien optimieren, in denen solche Typen als Zwischenwerte verwendet werden, andernfalls aber nicht verfügbar gemacht werden.
    • Es wird davon ausgegangen, dass ein Aufruf eines .AddRange(x)-Mitglieds einer Sammlung zu demselben Endwert führt, als wenn Sie über x iterieren und alle seine Aufzählungswerte einzeln mit .Addzur Sammlung hinzufügen.
    • Das Verhalten von Sammlungsliteralen mit Sammlungen, die sich nicht gut benehmen, ist undefiniert.

Umwandlungen

Eine Sammlungsausdruck-Konvertierung ermöglicht die Konvertierung eines Sammlungsausdrucks in einen Typ.

Es gibt eine implizite Sammlungsausdruck-Konvertierung von einem Sammlungsausdruck zu den folgenden Typen:

  • Ein eindimensionaler ArraytypT[], wobei der Elementtyp T ist.
  • Ein Span-Typ:
    • System.Span<T>
    • System.ReadOnlySpan<T>
      In diesem Fall ist der Elementtyp T
  • Ein -Typ mit einer geeigneten --Create-Methode. In diesem Fall ist der -Elementtyp der --Iterationstyp, der aus einer GetEnumerator-Instanzmethode oder einer Enumerable-Schnittstelle bestimmt wird, nicht aus einer Erweiterungsmethode.
  • Eine Struktur oder ein Klassentyp, der System.Collections.IEnumerable implementiert, wobei:
    • Der Typ hat einen anwendbaren Konstruktor, der ohne Argumente aufgerufen werden kann, und der Konstruktor ist an der Stelle des Sammlungsausdrucks zugänglich.

    • Wenn der Auflistungsausdruck Elemente enthält, hat der Typ eine Instanz- oder Erweiterungsmethode Add, wobei:

      • Die Methode kann mit einem einzelnen Wertargument aufgerufen werden.
      • Wenn die Methode generisch ist, können die Typargumente aus der Auflistung und dem Argument abgeleitet werden.
      • Die Methode ist am Speicherort des Sammlungsausdrucks zugänglich.

      In diesem Fall ist der -Elementtyp der Iterationstyp vom Typ .

  • Ein Schnittstellentyp:
    • System.Collections.Generic.IEnumerable<T>
    • System.Collections.Generic.IReadOnlyCollection<T>
    • System.Collections.Generic.IReadOnlyList<T>
    • System.Collections.Generic.ICollection<T>
    • System.Collections.Generic.IList<T>
      In diesem Fall ist der Elementtyp T

Die implizite Konvertierung existiert, wenn der Typ einen ElementtypT hat, wobei für jedes ElementEᵢ im Sammlungsausdruck:

  • Wenn Eᵢ ein Ausdruckselementist, gibt es eine implizite Konvertierung von Eᵢ in T.
  • Wenn Eᵢ ein Spread-Element..Sᵢist, gibt es eine implizite Konvertierung vom Iterationstyp von Sᵢ in T.

Es gibt keine Sammlungsausdruck-Konvertierung von einem Sammlungsausdruck zu einem mehrdimensionalen Array-Typ.

Typen, für die es eine implizite Konvertierung eines Sammlungsausdrucks aus einem Sammlungsausdruck gibt, sind die gültigen Zieltypen für diesen Sammlungsausdruck.

Es gibt die folgenden zusätzlichen impliziten Konvertierungen aus einem Sammlungsausdruck:

  • Zu einem nullbaren WerttypT? , bei dem es eine Sammlungsausdruck-Konvertierung vom Sammlungsausdruck zu einem Werttyp Tgibt. Die Konvertierung ist eine Sammlungsausdruck-Konvertierung nach T gefolgt von einer impliziten nullbaren Konvertierung von T nach T?.

  • Zu einem Referenztyp T , bei dem es eine Erstellungsmethode gibt, die mit T assoziiert ist und einen Typ U und eine implizite Referenzkonvertierung von U zu Tzurückgibt. Die Konvertierung ist eine Sammlungsausdruck-Konvertierung nach U gefolgt von einer impliziten Referenzkonvertierung von U nach T.

  • Zu einem Schnittstellentyp I , bei dem es eine Erstellungsmethode gibt, die mit I assoziiert ist und einen Typ V und eine implizite Boxing-Konvertierung von V nach Izurückgibt. Die Konvertierung ist eine Sammlungsausdruck-Konvertierung nach V gefolgt von einer impliziten Boxing-Konvertierung von V nach I.

Erstellen von Methoden

Eine Erstellungsmethode wird mit einem [CollectionBuilder(...)]-Attribut auf dem Sammlungstypangezeigt. Das Attribut gibt den Generatortyp und Methodennamen einer Methode an, die aufgerufen werden soll, um eine Instanz des Auflistungstyps zu erstellen.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(
        AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface,
        Inherited = false,
        AllowMultiple = false)]
    public sealed class CollectionBuilderAttribute : System.Attribute
    {
        public CollectionBuilderAttribute(Type builderType, string methodName);
        public Type BuilderType { get; }
        public string MethodName { get; }
    }
}

Das Attribut kann auf eine class, struct, ref structoder interfaceangewendet werden. Das Attribut wird nicht geerbt, obwohl das Attribut auf eine Basis-class oder eine abstract classangewendet werden kann.

Der Buildertyp muss ein nicht-generischer class oder structsein.

Zunächst wird die Menge der anwendbaren ErstellungsmethodenCM bestimmt.
Sie besteht aus Methoden, die die folgenden Anforderungen erfüllen:

  • Die Methode muss den im attribut [CollectionBuilder(...)] angegebenen Namen haben.
  • Die Methode muss für den Generatortyp direkt definiert werden.
  • Die Methode muss staticsein.
  • Die Methode muss dort zugänglich sein, wo der Sammlungsausdruck verwendet wird.
  • Die Sorte der Methode muss mit der Sorte des Sammlungstyps übereinstimmen.
  • Die Methode muss über einen einzelnen Parameter vom Typ System.ReadOnlySpan<E>verfügen, der nach Wert übergeben wird.
  • Es gibt eine Identitätskonvertierung, implizite Referenzkonvertierungoder Boxing-Konvertierung vom Rückgabetyp der Methode zum Sammlungstyp.

Methoden, die auf Basistypen oder Schnittstellen deklariert sind, werden ignoriert und sind nicht Teil der CM Menge.

Wenn die CM Menge leer ist, dann hat der Sammlungstyp keinen Elementtyp und hat keine Erstellungsmethode. Es gelten keine der folgenden Schritte.

Wenn nur eine Methode unter denen in der CM Menge eine Identitätskonvertierung von E zum Elementtyp des Sammlungstypshat, das ist die Erstellungsmethode für den Sammlungstyp. Andernfalls hat der Sammlungstyp keine Erstellungsmethode.

Wenn das attribut "[CollectionBuilder]" nicht auf eine aufrufende Methode mit der erwarteten Signatur verweist, wird ein Fehler gemeldet.

Für einen Auflistungsausdruck mit einem Zieltyp C<S0, S1, …>, in dem die TypdeklarationC<T0, T1, …> eine zugeordnete GeneratormethodeB.M<U0, U1, …>()hat, werden die generischen Typargumente aus dem Zieltyp in der Reihenfolge angewendet – vom äußersten enthaltenen Typ bis zum innersten – auf die Generatormethode.

Der span-Parameter für die Methode create kann explizit als scoped oder [UnscopedRef]markiert werden. Wenn der Parameter implizit oder explizit scopedist, kann der Compiler den Speicher für die Spanne auf dem Stack und nicht auf dem Heap zuweisen.

Ein Beispiel: Eine mögliche -Erstellungsmethode für ImmutableArray<T>:

[CollectionBuilder(typeof(ImmutableArray), "Create")]
public struct ImmutableArray<T> { ... }

public static class ImmutableArray
{
    public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items) { ... }
}

Mit der obigen Methode create könnte ImmutableArray<int> ia = [1, 2, 3]; als emittiert werden:

[InlineArray(3)] struct __InlineArray3<T> { private T _element0; }

Span<int> __tmp = new __InlineArray3<int>();
__tmp[0] = 1;
__tmp[1] = 2;
__tmp[2] = 3;
ImmutableArray<int> ia =
    ImmutableArray.Create((ReadOnlySpan<int>)__tmp);

Konstruktion

Die Elemente eines Sammlungsausdrucks werden in der Reihenfolge von links nach rechts ausgewertet . Jedes Element wird genau einmal ausgewertet, und alle weiteren Verweise auf die Elemente beziehen sich auf die Ergebnisse dieser Erstauswertung.

Ein Spreizungselement kann iteriert werden, bevor oder nachdem die nachfolgenden Elemente im Sammlungsausdruck ausgewertetwerden.

Eine unbehandelte Ausnahme, die von einer der während der Konstruktion verwendeten Methoden ausgelöst wird, wird nicht erfasst und verhindert weitere Schritte in der Konstruktion.

Length, Countund GetEnumerator werden davon ausgegangen, dass keine Nebenwirkungen auftreten.


Wenn der Zieltyp eine Struktur oder ein Klassentyp ist, der System.Collections.IEnumerableimplementiert, und der Zieltyp nicht über eine create-Methodeverfügt, wird die Sammlungsinstanz wie folgt konstruiert:

  • Die Elemente werden in der Reihenfolge ausgewertet. Einige oder alle Elemente können während der folgenden Schritte ausgewertet werden und nicht vorher.

  • Der Compiler kann die bekannte Länge des Sammlungsausdrucks bestimmen, indem er abzählbare Eigenschaften- oder äquivalente Eigenschaften von bekannten Schnittstellen oder Typen- für jeden Sammlungselement-Ausdruckaufruft.

  • Der Konstruktor, der ohne Argumente anwendbar ist, wird aufgerufen.

  • Für jedes Element in der Reihenfolge:

    • Wenn das Element ein Ausdruckselementist, wird die entsprechende Add Instanz oder Erweiterungsmethode mit dem Element Ausdruck als Argument aufgerufen. (Im Gegensatz zum klassischen Sammlungsinitialisierungsverhaltensind Elementauswertung und Add-Aufrufe nicht notwendigerweise verschachtelt).
    • Wenn das Element ein Spread-Element ist, wird eine der folgenden Optionen verwendet:
      • Eine anwendbare GetEnumerator-Instanz oder Erweiterungsmethode wird auf dem Spread-Element-Ausdruck ausgeführt, und für jedes Element vom Enumerator wird die anwendbare Add-Instanz oder Erweiterungsmethode auf der Sammlung-Instanz mit dem Element als Argument ausgeführt. Wenn der Enumerator IDisposableimplementiert, wird Dispose unabhängig von Ausnahmen nach enumeration aufgerufen.
      • Eine anwendbare AddRange Instanz oder Erweiterungsmethode wird auf der Sammlungsinstanz mit dem Spread-Element Ausdruck als Argument aufgerufen.
      • Eine anwendbare CopyTo Instanz oder Erweiterungsmethode wird auf dem Spread-Element-Ausdruck mit der Sammlungsinstanz und dem int Index als Argumente aufgerufen.
  • Während der obigen EnsureCapacityKonstruktionsschritte kann eine anwendbare Instanz oder Erweiterungsmethode ein oder mehrere Male auf der Sammlungsinstanz mit einem int Kapazitätsargument aufgerufen werden.


Wenn der Zieltyp ein Array, ein span, ein Typ mit einer Erstellungsmethodeoder ein Interfaceist, erfolgt die Konstruktion der Sammlungsinstanz wie folgt:

  • Die Elemente werden in der Reihenfolge ausgewertet. Einige oder alle Elemente können während der folgenden Schritte ausgewertet werden und nicht vorher.

  • Der Compiler kann die bekannte Länge des Sammlungsausdrucks bestimmen, indem er abzählbare Eigenschaften- oder äquivalente Eigenschaften von bekannten Schnittstellen oder Typen- für jeden Sammlungselement-Ausdruckaufruft.

  • Es wird eine Initialisierungsinstanz wie folgt erstellt:

    • Wenn der Zieltyp ein Array ist und der Sammlungsausdruck eine bekannte Längehat, wird ein Array mit der erwarteten Länge zugewiesen.
    • Wenn der Zieltyp ein Span oder ein Typ mit einer Erstellungsmethodeist und die Sammlung eine bekannte Längehat, wird ein Span mit der erwarteten Länge erstellt, der sich auf zusammenhängenden Speicher bezieht.
    • Andernfalls wird ein Zwischenspeicher zugewiesen.
  • Für jedes Element in der Reihenfolge:

    • Wenn das Element ein Ausdruckselementist, wird die Initialisierungsinstanz Indexer aufgerufen, um den ausgewerteten Ausdruck im aktuellen Index hinzuzufügen.
    • Wenn das Element ein Spread-Element ist, wird eine der folgenden Optionen verwendet:
      • Ein Element einer bekannten Schnittstelle oder eines bekannten Typs wird aufgerufen, um Elemente aus dem Spread-Elementausdruck in die Initialisierungsinstanz zu kopieren.
      • Eine anwendbare GetEnumerator Instanz oder Erweiterungsmethode wird auf dem Ausdruck des Elements spread aufgerufen und für jedes Element aus dem Enumerator wird die Initialisierungsinstanz Indexer aufgerufen, um das Element am aktuellen Index hinzuzufügen. Wenn der Enumerator IDisposableimplementiert, wird Dispose unabhängig von Ausnahmen nach enumeration aufgerufen.
      • Eine anwendbare CopyTo Instanz oder Erweiterungsmethode wird auf dem Spread-Element-Ausdruck mit der Initialisierungsinstanz und int Index als Argumente aufgerufen.
  • Wenn der Sammlung Zwischenspeicher zugewiesen wurde, wird eine Sammlungsinstanz mit der tatsächlichen Sammlungslänge zugewiesen, und die Werte aus der Initialisierungsinstanz werden in die Sammlungsinstanz kopiert, oder wenn eine Spanne erforderlich ist, kann der Compiler eine Spanne der tatsächlichen Sammlungslänge aus dem Zwischenspeicher verwenden. Andernfalls ist die Initialisierungsinstanz die Sammlungsinstanz.

  • Wenn der Zieltyp über eine Create-Methodeverfügt, wird die Create-Methode mit der Span-Instanz aufgerufen.


Hinweis: Der Compiler kann verzögern, indem er der Sammlung Elemente hinzufügt – oder verzögern, indem er durch verteilte Elemente iteriert- bis nach der Auswertung der nachfolgenden Elemente. (Wenn nachfolgende Spread-Elemente zählbare Eigenschaften aufweisen, die die Berechnung der erwarteten Länge der Auflistung vor der Zuordnung der Auflistung ermöglichen würden.) Im Gegensatz dazu kann der Compiler vorschnell Elemente zur Sammlung hinzufügen — und vorschnell durch Spread-Elemente iterieren — wenn es keinen Vorteil gibt, sich zu verzögern.

Betrachten Sie den folgenden Sammlungsausdruck:

int[] x = [a, ..b, ..c, d];

Wenn die Spread-Elemente b und czählbar sind, könnte der Compiler das Hinzufügen von Elementen aus a und b hinauszögern, bis c ausgewertet wurde, um die Zuordnung des resultierenden Arrays in der erwarteten Länge zu ermöglichen. Danach könnte der Compiler eifrig Elemente aus chinzufügen, bevor er dauswertet.

var __tmp1 = a;
var __tmp2 = b;
var __tmp3 = c;
var __result = new int[2 + __tmp2.Length + __tmp3.Length];
int __index = 0;
__result[__index++] = __tmp1;
foreach (var __i in __tmp2) __result[__index++] = __i;
foreach (var __i in __tmp3) __result[__index++] = __i;
__result[__index++] = d;
x = __result;

Leeres Sammlungsliteral

  • Das leere Literal [] hat keinen Typ. Ähnlich wie das Null-Literalkann dieses Literal jedoch implizit in einen beliebigen konstruierbaren Sammlungstyp umgewandelt werden.

    Das folgende Beispiel ist nicht zulässig, da es keinen Zieltyp gibt und keine anderen Konvertierungen beteiligt sind:

    var v = []; // illegal
    
  • Das Verteilen eines leeren Buchstabens ist erlaubt. Zum Beispiel:

    bool b = ...
    List<int> l = [x, y, .. b ? [1, 2, 3] : []];
    

    Wenn b falsch ist, ist es nicht erforderlich, dass tatsächlich ein Wert für den leeren Auflistungsausdruck erstellt wird, da er sofort in Nullwerte im endgültigen Literal verteilt wird.

  • Der leere Sammlungsausdruck darf ein Singleton sein, wenn er zum Erstellen eines endgültigen Sammlungswerts verwendet wird, von dem bekannt ist, dass er nicht veränderlich ist. Zum Beispiel:

    // Can be a singleton, like Array.Empty<int>()
    int[] x = []; 
    
    // Can be a singleton. Allowed to use Array.Empty<int>(), Enumerable.Empty<int>(),
    // or any other implementation that can not be mutated.
    IEnumerable<int> y = [];
    
    // Must not be a singleton.  Value must be allowed to mutate, and should not mutate
    // other references elsewhere.
    List<int> z = [];
    

Referenzsicherheit

Siehe safe-context constraint für Definitionen der safe-context Werte: Deklarationsblock, Funktionselementund Aufrufer-Kontext.

Der safe-context eines Sammlungsausdrucks ist:

  • Der sichere Kontext eines leeren Sammlungsausdrucks [] ist der Aufrufkontext.

  • Wenn der Zieltyp ein span-TypSystem.ReadOnlySpan<T>ist, und T einer der primitiven Typenbool, sbyte, byte, short, ushort, char, int, uint, long, ulong, floatoder double, und der Sammlungsausdruck enthält nur konstante Werte, ist der sichere Kontext des Sammlungsausdrucks der Aufrufer-Kontext.

  • Wenn der Zieltyp ein span-TypSystem.Span<T> oder System.ReadOnlySpan<T>ist, ist der sichere Kontext des Sammlungsausdrucks der Deklarationsblock.

  • Wenn der Zieltyp ein ref struct-Typ mit einer create-Methodeist, ist der safe-context des collection-Ausdrucks der safe-context eines Aufrufs der create-Methode, bei dem der collection-Ausdruck das span-Argument der Methode ist.

  • Andernfalls ist der sichere Kontext des Sammlungsausdrucks der Aufrufer-Kontext.

Ein Ausdruck für eine Sammlung mit einem sicheren Kontext von Deklarationsblock kann dem umschließenden Bereich nicht entkommen, und der Compiler kann die Sammlung auf dem Stack statt auf dem Heap speichern.

Damit ein Auflistungsausdruck für einen ref struct-Typ den Deklarationsblockumgehen kann, kann es notwendig sein, den Ausdruck auf einen anderen Typ zu übertragen.

static ReadOnlySpan<int> AsSpanConstants()
{
    return [1, 2, 3]; // ok: span refers to assembly data section
}

static ReadOnlySpan<T> AsSpan2<T>(T x, T y)
{
    return [x, y];    // error: span may refer to stack data
}

static ReadOnlySpan<T> AsSpan3<T>(T x, T y, T z)
{
    return (T[])[x, y, z]; // ok: span refers to T[] on heap
}

Typinferenz

var a = AsArray([1, 2, 3]);          // AsArray<int>(int[])
var b = AsListOfArray([[4, 5], []]); // AsListOfArray<int>(List<int[]>)

static T[] AsArray<T>(T[] arg) => arg;
static List<T[]> AsListOfArray<T>(List<T[]> arg) => arg;

Die Typ-Inferenz-Regeln werden wie folgt aktualisiert.

Die bestehenden Regeln für die Erste Phase werden in einen neuen Abschnitt Eingabetyp-Inferenz extrahiert, und eine Regel wird zu Eingabetyp-Inferenz und Ausgabetyp-Inferenz für Sammlungsausdrücke hinzugefügt.

11.6.3.2 Die erste Phase

Für jedes der Methodenargumente Eᵢ:

  • Eine Eingabetyp-Inferenz wird vonEᵢzu dem entsprechenden ParametertypTᵢgemacht.

Eine Eingabetyp-Inferenz wird von einem Ausdruck Ezu einem Typ T auf folgende Weise vorgenommen:

  • Wenn E ein Sammlungsausdruck mit Elementen Eᵢist, und T ist ein Typ mit einem ElementtypTₑ oder T ist ein nullbarer WerttypT0? und T0 hat einen ElementtypTₑ, dann gilt für jedes Eᵢ:
    • Wenn Eᵢ ein Ausdruckselementist, dann wird eine Eingabetyp-Inferenz gemacht vonEᵢnachTₑ.
    • Wenn Eᵢ ein Spreizungselement mit einem IterationstypSᵢist, dann wird eine Untergrenze-Inferenz von nachSᵢbisTₑgemacht.
  • [bestehende Regeln aus der ersten Phase] ...

11.6.3.7 Ausgabetyp-Inferenzen

Eine Ausgangstyp-Inferenz wird von einem Ausdruck Ezu einem Typ T auf folgende Weise vorgenommen:

  • Wenn E ein Sammlungsausdruck mit Elementen Eᵢist, und T ist ein Typ mit einem ElementtypTₑ oder T ist ein nullbarer WerttypT0? und T0 hat einen ElementtypTₑ, dann gilt für jedes Eᵢ:
    • Wenn Eᵢ ein Ausdruckselementist, dann wird eine Ausgangstyp-Inferenz gemacht vonEᵢnachTₑ.
    • Wenn Eᵢ ein Spread-Elementist, wird keine Ableitung aus Eᵢhergestellt.
  • [vorhandene Regeln aus Ausgabetypinferenz] ...

Erweiterungsmethoden

Keine Änderungen an den Regeln für Aufrufe von Erweiterungsmethoden .

12.8.10.3 Erweiterungsmethodeaufrufe

Eine Erweiterungsmethode Cᵢ.Mₑ ist wählbar , wenn:

  • ...
  • Es gibt eine implizite Identitäts-, Referenz- oder Boxing-Konvertierung von expr zum Typ des ersten Parameters von Mₑ.

Ein Auflistungsausdruck hat keinen natürlichen Typ, sodass die vorhandenen Konvertierungen vom Typ auf Typ nicht anwendbar sind. Daher kann ein Sammlungsausdruck nicht direkt als erster Parameter für einen Aufruf einer Erweiterungsmethode verwendet werden.

static class Extensions
{
    public static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
}

var x = [1].AsImmutableArray();           // error: collection expression has no target type
var y = [2].AsImmutableArray<int>();      // error: ...
var z = Extensions.AsImmutableArray([3]); // ok

Überladungsauflösung

Bessere Konvertierung von Ausdruck wurde aktualisiert, um bestimmte Zieltypen bei der Konvertierung von Sammelausdrücken zu bevorzugen.

In den aktualisierten Regeln:

  • Ein span_type ist eines von:
    • System.Span<T>
    • System.ReadOnlySpan<T>.
  • Ein array_oder_array_interface ist eines von:
    • ein Array Typ
    • einer der folgenden Schnittstellentypen , die von einem Array-Typimplementiert werden:
      • System.Collections.Generic.IEnumerable<T>
      • System.Collections.Generic.IReadOnlyCollection<T>
      • System.Collections.Generic.IReadOnlyList<T>
      • System.Collections.Generic.ICollection<T>
      • System.Collections.Generic.IList<T>

Aufgrund einer impliziten Konvertierung C₁, die von einem Ausdruck E in einen Typ T₁konvertiert wird, und einer impliziten Konvertierung C₂, die von einem Ausdruck E in einen Typ T₂konvertiert wird, ist C₁ eine bessere Konvertierung als C₂, wenn einer der folgenden Haltebereiche gilt:

  • E ist ein Sammlungsausdruck und eine der folgenden Bedingungen ist erfüllt:
    • T₁ ist System.ReadOnlySpan<E₁>, und T₂ ist System.Span<E₂>, und eine implizite Konvertierung ist von E₁ in E₂ vorhanden.
    • T₁ ist System.ReadOnlySpan<E₁> oder System.Span<E₁>, und T₂ ist ein array_oder_array_interface mit ElementtypE₂, und es gibt eine implizite Umwandlung von E₁ nach E₂
    • T₁ ist kein span_type, und T₂ ist kein span_type, und eine implizite Umwandlung von T₁ zu T₂ existiert.
  • E ist kein Sammlungs-Ausdruck und eine der folgenden Bedingungen gilt:
    • E stimmt genau mit T₁ überein, und E stimmt nicht genau mit T₂
    • E stimmt genau entweder mit beiden von T₁ und T₂überein oder mit keinem von beiden, und T₁ ist ein besseres Konvertierungsziel als T₂
  • E ist eine Methodengruppe, ...

Beispiele für Unterschiede bei der Überladungsauflösung zwischen Arrayinitialisierern und Auflistungsausdrücken:

static void Generic<T>(Span<T> value) { }
static void Generic<T>(T[] value) { }

static void SpanDerived(Span<string> value) { }
static void SpanDerived(object[] value) { }

static void ArrayDerived(Span<object> value) { }
static void ArrayDerived(string[] value) { }

// Array initializers
Generic(new[] { "" });      // string[]
SpanDerived(new[] { "" });  // ambiguous
ArrayDerived(new[] { "" }); // string[]

// Collection expressions
Generic([""]);              // Span<string>
SpanDerived([""]);          // Span<string>
ArrayDerived([""]);         // ambiguous

Span-Typen

Die span-Typen ReadOnlySpan<T> und Span<T> sind beide konstruierbare Sammlungstypen. Die Unterstützung für sie folgt dem Design für params Span<T>. Genauer gesagt, führt das Konstruieren einer dieser Spans zu einem Array T[], das auf dem Stapel erstellt wird, wenn das Array params innerhalb der vom Compiler festgelegten Grenzen liegt (falls vorhanden). Andernfalls wird das Array auf dem Heap zugewiesen.

Wenn der Compiler sich dafür entscheidet, auf dem Stack zu allozieren, ist es nicht erforderlich, ein Literal direkt in ein stackalloc an diesem bestimmten Punkt zu übersetzen. Angenommen, dies liegt vor:

foreach (var x in y)
{
    Span<int> span = [a, b, c];
    // do things with span
}

Der Compiler darf das mit stackalloc übersetzen, solange die Bedeutung von Span gleich bleibt und span-safety beibehalten wird. Zum Beispiel kann es den obigen Text folgendermaßen übersetzen:

Span<int> __buffer = stackalloc int[3];
foreach (var x in y)
{
    __buffer[0] = a
    __buffer[1] = b
    __buffer[2] = c;
    Span<int> span = __buffer;
    // do things with span
}

Der Compiler kann auch Inline-Arraysverwenden, falls verfügbar, wenn er sich für eine Allokation auf dem Stack entscheidet. Beachten Sie, dass in C# 12 Inlinearrays nicht mit einem Auflistungsausdruck initialisiert werden können. Dieses Feature ist ein offener Vorschlag.

Wenn der Compiler sich für eine Allokation auf dem Heap entscheidet, ist die Übersetzung für Span<T> einfach:

T[] __array = [...]; // using existing rules
Span<T> __result = __array;

Sammlung Literalübersetzung

Ein Sammlungsausdruck hat eine bekannte Länge , wenn der Kompilierzeittyp jedes Sammelelements im Sammlungsausdruck abzählbarist.

Schnittstellenübersetzung

Nicht veränderbare Schnittstellenübersetzung

Bei einem Zieltyp, der keine verändernden Mitglieder enthält, nämlich IEnumerable<T>, IReadOnlyCollection<T>und IReadOnlyList<T>, ist eine konforme Implementierung erforderlich, um einen Wert zu erzeugen, der diese Schnittstelle implementiert. Wenn ein Typ synthetisiert wird, wird empfohlen, dass der synthetisierte Typ alle diese Schnittstellen sowie ICollection<T> und IList<T>implementiert, unabhängig davon, welcher Schnittstellentyp gezielt war. Dies gewährleistet eine maximale Kompatibilität mit vorhandenen Bibliotheken, einschließlich solcher, die die von einem Wert implementierten Schnittstellen betrachten, um Performance-Optimierungen zu beleuchten.

Darüber hinaus muss der Wert die nichtgenerischen ICollection und IList Schnittstellen implementieren. Auf diese Weise können Sammlungsausdrücke die dynamische Introspektion in Szenarien wie datenbindung unterstützen.

Eine kompatible Implementierung ist kostenlos für:

  1. Verwenden Sie einen vorhandenen Typ, der die erforderlichen Schnittstellen implementiert.
  2. Synthetisieren Sie einen Typ, der die erforderlichen Schnittstellen implementiert.

In beiden Fällen darf der verwendete Typ einen größeren Satz von Schnittstellen implementieren, als unbedingt erforderlich ist.

Synthetisierte Typen können jede Strategie verwenden, mit der sie die erforderlichen Schnittstellen ordnungsgemäß implementieren möchten. Beispielsweise kann ein synthetisierter Typ die Elemente direkt in sich selbst inlineieren und die Notwendigkeit zusätzlicher interner Sammlungszuweisungen vermeiden. Ein synthetisierter Typ konnte auch keinen Speicher verwenden, indem er die Werte direkt berechnet. Zum Beispiel die Rückgabe von index + 1 für [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].

  1. Der Wert muss true zurückgeben, wenn er für ICollection<T>.IsReadOnly (falls implementiert) und nicht-generische IList.IsReadOnly und IList.IsFixedSizeabgefragt wird. Dadurch wird sichergestellt, dass Verbraucher erkennen können, dass es sich um eine nicht veränderbare Sammlung handelt, obwohl veränderbare Ansichten implementiert wurden.
  2. Der Wert muss bei jedem Aufruf einer Mutationsmethode (wie IList<T>.Add) ausgelöst werden. Dies sorgt für Sicherheit und verhindert, dass eine nicht veränderbare Sammlung versehentlich verändert wird.

Änderbare Schnittstellenübersetzung

Gegebener Zieltyp, der veränderliche Mitglieder enthält, nämlich ICollection<T> oder IList<T>:

  1. Der Wert muss eine Instanz von List<T>sein.

Übersetzung der bekannten Länge

Eine bekannte Länge ermöglicht eine effiziente Konstruktion eines Ergebnisses ohne das Kopieren von Daten und ohne unnötigen Schlupfspeicher in einem Ergebnis.

Das Fehlen einer bekannten Länge verhindert nicht, dass ein Ergebnis erstellt wird. Dies kann jedoch zu zusätzlichen CPU- und Arbeitsspeicherkosten führen, wenn die Daten erzeugt und dann zum endgültigen Ziel verschoben werden.

  • Für ein Bekannte Länge-Literal [e1, ..s1, etc]beginnt die Übersetzung zunächst mit folgendem:

    int __len = count_of_expression_elements +
                __s1.Count;
                ...
                __s_n.Count;
    
  • Angesichts eines Zieltyps T für dieses Literal:

    • Wenn T ein T1[]ist, dann wird das Wörtchen mit übersetzt:

      T1[] __result = new T1[__len];
      int __index = 0;
      
      __result[__index++] = __e1;
      foreach (T1 __t in __s1)
          __result[__index++] = __t;
      
      // further assignments of the remaining elements
      

      Die Implementierung darf andere Mittel verwenden, um das Array aufzufüllen. Verwenden Sie beispielsweise effiziente Massenkopiemethoden wie .CopyTo().

    • Wenn T ein Span<T1>ist, wird das Literal wie oben beschrieben übersetzt, mit der Ausnahme, dass die __result Initialisierung wie folgt übersetzt wird:

      Span<T1> __result = new T1[__len];
      
      // same assignments as the array translation
      

      Die Übersetzung kann stackalloc T1[] oder ein Inline-Array anstelle von new T1[] verwenden, wenn span-safety beibehalten wird.

    • Wenn T ein ReadOnlySpan<T1>ist, dann wird das Literal genauso übersetzt wie im Fall Span<T1> , außer dass das Endergebnis ist, dass Span<T1>implizit in ein ReadOnlySpan<T1>umgewandelt wurde.

      Ein ReadOnlySpan<T1> , bei dem T1 ein primitiver Typ ist und alle Elemente der Sammlung konstant sind, braucht seine Daten nicht auf dem Heap oder dem Stack zu haben. Beispielsweise könnte eine Implementierung diese Spanne direkt als Verweis auf einen Teil des Datensegments des Programms erstellen.

      Die obigen Formulare (für Arrays und Spannweiten) sind die Basisdarstellungen des Sammlungsausdrucks und werden für die folgenden Übersetzungsregeln verwendet:

      • Wenn T ein C<S0, S1, …> ist, das eine entsprechende ErstellungsmethodeB.M<U0, U1, …>()hat, dann wird das Literal wie folgt übersetzt:

        // Collection literal is passed as is as the single B.M<...>(...) argument
        C<S0, S1, …> __result = B.M<S0, S1, …>([...])
        

        Da die create-Methode einen Argumenttyp eines instanziierten ReadOnlySpan<T>haben muss, gilt die Übersetzungsregel für Spans, wenn der Ausdruck für die Sammlung an die create-Methode übergeben wird.

      • Wenn T Sammlungsinitialisiererunterstützt, dann:

        • wenn der Typ T einen barrierefreien Konstruktor mit einem einzelnen Parameter int capacityenthält, wird das Literal übersetzt als:

          T __result = new T(capacity: __len);
          __result.Add(__e1);
          foreach (var __t in __s1)
              __result.Add(__t);
          
          // further additions of the remaining elements
          

          Hinweis: Der Name des Parameters muss capacitywerden.

          Dieses Formular ermöglicht es, einen Literalwert zu verwenden, um den neu erstellten Typ in Bezug auf die Anzahl der Elemente zu spezifizieren, um eine effiziente Zuweisung des internen Speichers zu ermöglichen. Dies vermeidet verschwenderische Neuzuweisungen, wenn die Elemente hinzugefügt werden.

        • andernfalls wird das Literal wie folgt übersetzt:

          T __result = new T();
          
          __result.Add(__e1);
          foreach (var __t in __s1)
              __result.Add(__t);
          
          // further additions of the remaining elements
          

          Dies ermöglicht das Erstellen des Zieltyps, wenn auch ohne Kapazitätsoptimierung, um die interne Verschiebung des Speichers zu verhindern.

Übersetzung unbekannter Länge

  • Gegeben ein Zieltyp T für ein unbekannte Länge Literal:

    • Wenn T Sammlungsinitialisiererunterstützt, dann wird das Literal als übersetzt:

      T __result = new T();
      
      __result.Add(__e1);
      foreach (var __t in __s1)
          __result.Add(__t);
      
      // further additions of the remaining elements
      

      Dies ermöglicht die Ausbreitung jeglicher iterierbarer Typen, auch wenn dies mit dem geringsten Maß an Optimierung erfolgt.

    • Wenn T einige T1[]ist, hat das Literal die gleiche Semantik wie:

      List<T1> __list = [...]; /* initialized using predefined rules */
      T1[] __result = __list.ToArray();
      

      Die obige Methode ist jedoch ineffizient; sie erstellt die Zwischenliste und erstellt daraus eine Kopie des endgültigen Arrays. Implementierungen haben die Freiheit, dies wegzuoptimieren, zum Beispiel Code zu erzeugen wie folgt:

      T1[] __result = <private_details>.CreateArray<T1>(
          count_of_expression_elements);
      int __index = 0;
      
      <private_details>.Add(ref __result, __index++, __e1);
      foreach (var __t in __s1)
          <private_details>.Add(ref __result, __index++, __t);
      
      // further additions of the remaining elements
      
      <private_details>.Resize(ref __result, __index);
      

      Dies ermöglicht minimale Verschwendung und Kopieren, ohne dass zusätzlichen Aufwand für Bibliothekssammlungen entstehen kann.

      Die an CreateArray übergebenen Zählungen werden verwendet, um einen Hinweis auf die Anfangsgröße bereitzustellen, um unnötige Größenänderungen zu vermeiden.

    • Wenn T ein span-Typist, kann eine Implementierung der obigen T[]-Strategie oder einer anderen Strategie mit der gleichen Semantik, aber besserer Leistung folgen. Anstatt z. B. das Array als Kopie der Listenelemente zuzuweisen, könnte CollectionsMarshal.AsSpan(__list) verwendet werden, um einen Spanwert direkt abzurufen.

Nicht unterstützte Szenarien

Während Sammlungsliterale für viele Szenarien verwendet werden können, gibt es einige, die sie nicht ersetzen können. Dazu gehören:

  • Mehrdimensionale Arrays (z. B. new int[5, 10] { ... }). Es gibt keine Möglichkeit, die Dimensionen einzubeziehen, und alle Sammlungsliterale sind entweder nur lineare oder nur Kartenstrukturen.
  • Auflistungen, die spezielle Werte an ihre Konstruktoren übergeben. Es gibt keine Möglichkeit, auf den verwendeten Konstruktor zuzugreifen.
  • Verschachtelte Sammlungsinitialisierer, z.B. new Widget { Children = { w1, w2, w3 } }. Dieses Formular muss bleiben, da es eine sehr unterschiedliche Semantik von Children = [w1, w2, w3]hat. Ersteres ruft .Add wiederholt auf .Children auf, während letzteres eine neue Sammlung über .Childrenzuweisen würde. Wir könnten in Erwägung ziehen, dass die letztere Form auf das Hinzufügen zu einer bestehenden Sammlung zurückgreift, wenn .Children nicht zugewiesen werden kann, aber das scheint extrem verwirrend zu sein.

Syntaxunklarheiten

  • Es gibt zwei echte syntaktische Mehrdeutigkeiten, bei denen es mehrere legale syntaktische Interpretationen des Codes gibt, der eine collection_literal_expressionverwendet.

    • Das spread_element ist zweideutig mit einem range_expression. Man könnte technisch folgendes haben:

      Range[] ranges = [range1, ..e, range2];
      

      Um dies zu beheben, können wir eine der folgenden Aktionen ausführen:

      • Erfordern Sie, dass Benutzer (..e) in Klammern setzen oder einen Startindex 0..e einschließen, wenn sie einen Bereich festlegen wollen.
      • Wählen Sie eine andere Syntax (z. B. ...) für die Verbreitung aus. Dies wäre bedauerlich für die mangelnde Konsistenz mit Schnittmustern.
  • Es gibt zwei Fälle, in denen es keine echte Mehrdeutigkeit gibt, aber in denen die Syntax die Analysekomplexität erheblich erhöht. Obwohl es kein Problem hinsichtlich der Ingenieurzeit darstellt, erhöht dies dennoch den kognitiven Aufwand für Benutzer beim Betrachten des Codes.

    • Zweideutigkeit zwischen collection_literal_expression und attributes bei Anweisungen oder lokalen Funktionen. Erwägen:

      [X(), Y, Z()]
      

      Dies kann eine der folgenden Sein:

      // A list literal inside some expression statement
      [X(), Y, Z()].ForEach(() => ...);
      
      // The attributes for a statement or local function
      [X(), Y, Z()] void LocalFunc() { }
      

      Ohne ein komplexes Voraussehen wäre es unmöglich zu sagen, ohne die Gesamtheit des Wörtlichen zu verbrauchen.

      Optionen zur Behebung dieses Problems umfassen folgende:

      • Lassen Sie dies zu, indem Sie die Analysearbeiten ausführen, um zu bestimmen, welche dieser Fälle dies ist.
      • Lassen Sie dies nicht zu und verlangen Sie, dass der Benutzer das Literal in Klammern wie ([X(), Y, Z()]).ForEach(...)einschließt.
      • Zweideutigkeit zwischen einem collection_literal_expression in einem conditional_expression und einem null_conditional_operations. Erwägen:
      M(x ? [a, b, c]
      

      Dies kann eine der folgenden Sein:

      // A ternary conditional picking between two collections
      M(x ? [a, b, c] : [d, e, f]);
      
      // A null conditional safely indexing into 'x':
      M(x ? [a, b, c]);
      

      Ohne ein komplexes Voraussehen wäre es unmöglich zu sagen, ohne die Gesamtheit des Wörtlichen zu verbrauchen.

      Hinweis: Dies ist auch ohne einen natürlichen Typ ein Problem, da die Zieltypisierung durch conditional_expressionserfolgt.

      Wie bei den anderen könnten wir Klammern erfordern, um Unklarheiten zu vermeiden. Mit anderen Worten, nehmen Sie die null_conditional_operation-Auslegung an, es sei denn, es ist wie folgt geschrieben: x ? ([1, 2, 3]) :. Das scheint jedoch ziemlich unglücklich zu sein. Diese Art von Code scheint nicht unvernünftig zu schreiben und wird wahrscheinlich Leute stolpern lassen.

Nachteile

  • Damit wird eine weitere Form für Sammlungsausdrücke eingeführt, zusätzlich zu den unzähligen Möglichkeiten, die wir bereits haben. Dies ist eine zusätzliche Komplexität für die Sprache. Dies ermöglicht jedoch auch die Vereinheitlichung einer -Ring--Syntax, die alle regiert, was bedeutet, dass bestehende Codebasen vereinfacht und überall in ein einheitliches Erscheinungsbild gebracht werden können.
  • Die Verwendung von [...] anstelle von {...} weicht von der Syntax ab, die wir in der Regel bereits für Arrays und Sammelinitialisierer verwendet haben. Es verwendet insbesondere [...] anstelle von {...}. Dies wurde jedoch bereits vom Sprachteam vereinbart, als wir Listenmuster festlegten. Wir haben versucht, {...} mit Listenmustern zum Laufen zu bringen, aber dabei traten unüberwindbare Probleme auf. Aus diesem Grund zogen wir zu [...], die sich, während sie für C# neu ist, in vielen Programmiersprachen natürlich fühlt und uns erlaubte, ohne Mehrdeutigkeit neu zu beginnen. Die Verwendung von [...] als entsprechende Literalform ergänzt unsere neuesten Entscheidungen und bietet uns eine saubere Grundlage, um ohne Probleme zu arbeiten.

Dies bringt Fehler in die Sprache. Zum Beispiel sind die folgenden Beispiele sowohl legal als auch (glücklicherweise) bedeutungsgleich.

int[] x = { 1, 2, 3 };
int[] x = [ 1, 2, 3 ];

Angesichts der Breite und Konsistenz, die durch die neue Literalsyntax gebracht wird, sollten wir jedoch in Betracht ziehen, dass Benutzer zur neuen Form wechseln. IDE-Vorschläge und Korrekturen könnten in diesem Zusammenhang hilfreich sein.

Alternativen

  • Welche anderen Designs wurden berücksichtigt? Welche Auswirkungen hat es, dies nicht zu tun?

Gelöste Fragen

  • Soll der Compiler stackalloc für die Stapelzuweisung verwenden, wenn Inline-Arrays nicht verfügbar sind und der Iterationstyp ein primitiver Typ ist?

    Entschluss: Nein. Das Verwalten eines stackalloc Puffers erfordert mehr Aufwand im Vergleich zu einem Inlinearray, um sicherzustellen, dass der Puffer nicht wiederholt zugewiesen wird, wenn sich der Sammlungsausdruck in einer Schleife befindet. Die zusätzliche Komplexität im Compiler und im generierten Code überwiegt den Vorteil der Stapelzuweisung auf älteren Plattformen.

  • In welcher Reihenfolge sollten literale Elemente im Vergleich zur Auswertung der Length/Count-Eigenschaft ausgewertet werden? Sollten wir zuerst alle Elemente auswerten, dann alle Längen? Oder sollten wir ein Element auswerten, dann seine Länge, dann das nächste Element usw.?

    Lösung: Zunächst werden alle Elemente ausgewertet, dann folgt alles andere.

  • Kann ein unbekannte Länge-Literal einen Sammlungstyp erstellen, der eine bekannte Längebenötigt, wie eine Array-, Span- oder Construct(array/span)-Sammlung? Dies wäre schwieriger, effizient zu tun, aber es könnte durch den geschickten Einsatz von gepoolten Arrays und/oder Buildern möglich sein.

    Lösung: Ja, wir erlauben die Erstellung einer Sammlung mit fester Länge aus einem Literal unbekannter Länge . Der Compiler darf dies so effizient wie möglich implementieren.

    Der folgende Text ist vorhanden, um die ursprüngliche Diskussion dieses Themas aufzuzeichnen.

    Benutzer können ein unbekannte Länge-Literal jederzeit in ein bekannte Länge-Literal umwandeln, und zwar mit Code wie diesem:

    ImmutableArray<int> x = [a, ..unknownLength.ToArray(), b];
    

    Dies ist jedoch aufgrund der Notwendigkeit, die Zuteilung temporärer Speicher zu erzwingen, bedauerlich. Wir könnten potenziell effizienter sein, wenn wir kontrollierten, wie dies ausgegeben wurde.

  • Kann eine collection_expression auf eine IEnumerable<T> oder andere Sammlungsschnittstellen ausgerichtet werden?

    Zum Beispiel:

    void DoWork(IEnumerable<long> values) { ... }
    // Needs to produce `longs` not `ints` for this to work.
    DoWork([1, 2, 3]);
    

    Lösung: Ja, ein Literal kann auf jeden Interfacetyp I<T> , den List<T> implementiert, als Ziel gesetzt werden. Beispiel: IEnumerable<long>. Dies ist dasselbe wie eine Zieltypisierung auf List<long> und die anschließende Zuweisung dieses Ergebnisses an den angegebenen Schnittstellentyp. Der folgende Text ist vorhanden, um die ursprüngliche Diskussion dieses Themas aufzuzeichnen.

    Die offene Frage hier ist, zu bestimmen, welcher zugrunde liegende Typ tatsächlich erstellt werden soll. Eine Möglichkeit ist, sich den Vorschlag für params IEnumerable<T>anzusehen. Dort würden wir ein Array generieren, das die Werte weitergibt, ähnlich wie bei params T[].

  • Kann/sollte der Compiler Array.Empty<T>() für []ausgeben? Sollte dies vorgeschrieben werden, um Zuweisungen nach Möglichkeit zu vermeiden?

    Ja. Der Compiler sollte Array.Empty<T>() für jeden Fall ausgeben, in dem dies legal ist und das Endergebnis nicht änderbar ist. Zum Beispiel, wenn man T[], IEnumerable<T>, IReadOnlyCollection<T> oder IReadOnlyList<T>anvisiert. Es sollte nicht Array.Empty<T> verwendet werden, wenn das Ziel änderbar ist (ICollection<T> oder IList<T>).

  • Sollten wir die Sammlungsinitialisierer erweitern, um nach der sehr häufigen Methode AddRange zu suchen? Es könnte von dem zugrunde liegenden konstruierten Typ verwendet werden, um das Hinzufügen von Spread-Elementen potenziell effizienter durchzuführen. Wir sollten vielleicht auch nach Dingen wie .CopyTo suchen. Hier kann es zu Nachteilen kommen, da diese Methoden am Ende zu übermäßigen Zuweisungen/Versendungen im Vergleich zur direkten Aufzählung im übersetzten Code führen können.

    Ja. Eine Implementierung darf andere Methoden verwenden, um einen Sammlungswert zu initialisieren, unter der Annahme, dass diese Methoden über eine klar definierte Semantik verfügen und dass Sammlungstypen "gut verhalten" sein sollten. In der Praxis sollte eine Implementierung jedoch vorsichtig sein, da Vorteile in einer Hinsicht (Massenkopieren) auch negative Folgen haben können (z. B. das Boxen einer Struktursammlung).

    Eine Implementierung sollte die Vorteile nutzen, wo es keine Nachteile gibt. Beispielsweise mit einer .AddRange(ReadOnlySpan<T>)-Methode.

Ungelöste Fragen

  • Sollten wir das Ableiten des Elementtyps des zulassen, wenn der Iterationstyp des "mehrdeutig" ist (nach irgendeiner Definition)? Zum Beispiel:
Collection x = [1L, 2L];

// error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable<T>'; try casting to a specific interface instantiation
foreach (var x in new Collection) { }

static class Builder
{
    public Collection Create(ReadOnlySpan<long> items) => throw null;
}

[CollectionBuilder(...)]
class Collection : IEnumerable<int>, IEnumerable<string>
{
    IEnumerator<int> IEnumerable<int>.GetEnumerator() => throw null;
    IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}
  • Sollte es legal sein, ein Sammlungsliteral zu erstellen und sofort zu indizieren? Hinweis: Dies erfordert eine Antwort auf die unten stehende ungelöste Frage, ob Sammlungsliterale einen natürlichen Typhaben.

  • Stapelzuweisungen für riesige Sammlungen könnten den Stapel sprengen. Sollte der Compiler eine Heuristik zum Platzieren dieser Daten auf dem Heap haben? Sollte die Sprache nicht angegeben werden, um diese Flexibilität zu ermöglichen? Wir sollten den Spezifikationen für params Span<T>folgen.

  • Müssen wir spread_elementals Zieltyp festlegen? Betrachten Sie z. B.:

    Span<int> span = [a, ..b ? [c] : [d, e], f];
    

    Hinweis: Dies kann häufig in der folgenden Form auftreten, um die bedingte Einbeziehung einiger Elemente oder nichts zuzulassen, wenn die Bedingung falsch ist:

    Span<int> span = [a, ..b ? [c, d, e] : [], f];
    

    Um dieses vollständige Literal auszuwerten, müssen wir die darin enthaltenen Elementausdrücke auswerten. Das bedeutet, dass man b ? [c] : [d, e]auswerten kann. Ohne einen Zieltyp, der diesen Ausdruck im Kontext auswertet, und ohne irgendeine Art von natürlichem Typwären wir jedoch nicht in der Lage festzustellen, was wir mit [c] oder [d, e] hier tun sollen.

    Um dies zu beheben, könnten wir sagen, dass beim Auswerten des spread_element Ausdrucks eines Literals ein impliziter Zieltyp vorhanden war, der dem Zieltyp des Literals selbst entspricht. Das würde also im obigen Sinne wie folgt umgeschrieben:

    int __e1 = a;
    Span<int> __s1 = b ? [c] : [d, e];
    int __e2 = f;
    
    Span<int> __result = stackalloc int[2 + __s1.Length];
    int __index = 0;
    
    __result[__index++] = a;
    foreach (int __t in __s1)
      __result[index++] = __t;
    __result[__index++] = f;
    
    Span<int> span = __result;
    

Die Spezifikation eines konstruierbaren Sammlungstyps unter Verwendung einer Erstellungsmethode ist empfindlich gegenüber dem Kontext, in dem die Konvertierung klassifiziert wird

Die Existenz der Umwandlung hängt in diesem Fall von der Vorstellung eines Iterationstyps des Sammlungstypsab. Wenn eine Erstellungs-Methode vorhanden ist, die ein ReadOnlySpan<T> akzeptiert, wobei T der Iterationstypist, existiert die Umwandlung. Andernfalls nicht.

Ein Iterationstyp ist jedoch gegenüber dem Kontext empfindlich, in dem foreach erfolgt. Für denselben Sammlungstyp kann es je nach den zur Verfügung stehenden Erweiterungsmethoden unterschiedlich sein, und es kann auch undefiniert sein.

Das ist für den Zweck von foreach in Ordnung, wenn der Typ nicht so konzipiert ist, dass er sich selbst vorhersehen lässt. Wenn dies der Fall ist, können Erweiterungsmethoden nicht ändern, wie der Typ vorhergesagt wird, unabhängig vom Kontext.

Das fühlt sich jedoch etwas seltsam an, wenn eine Konvertierung kontextempfindlich ist. Tatsächlich ist die Konvertierung "instabil". Ein Sammlungstyp , der ausdrücklich konstruierbar sein soll, darf die Definition eines sehr wichtigen Details auslassen- seinen Iterationstyp. Lassen Sie den Typ "nicht konvertierbar" auf sich selbst.

Hier ist ein Beispiel:

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))]
class MyCollection
{
}
class MyCollectionBuilder
{
    public static MyCollection Create(ReadOnlySpan<long> items) => throw null;
    public static MyCollection Create(ReadOnlySpan<string> items) => throw null;
}

namespace Ns1
{
    static class Ext
    {
        public static IEnumerator<long> GetEnumerator(this MyCollection x) => throw null;
    }
    
    class Program
    {
        static void Main()
        {
            foreach (var l in new MyCollection())
            {
                long s = l;
            }
        
            MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
                               2];
        }
    }
}

namespace Ns2
{
    static class Ext
    {
        public static IEnumerator<string> GetEnumerator(this MyCollection x) => throw null;
    }
    
    class Program
    {
        static void Main()
        {
            foreach (var l in new MyCollection())
            {
                string s = l;
            }
        
            MyCollection x1 = ["a",
                               2]; // error CS0029: Cannot implicitly convert type 'int' to 'string'
        }
    }
}

namespace Ns3
{
    class Program
    {
        static void Main()
        {
            // error CS1579: foreach statement cannot operate on variables of type 'MyCollection' because 'MyCollection' does not contain a public instance or extension definition for 'GetEnumerator'
            foreach (var l in new MyCollection())
            {
            }
        
            MyCollection x1 = ["a", 2]; // error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type.
        }
    }
}

Wenn der Typ Iterationstyp selbst nicht definiert, kann der Compiler aufgrund des aktuellen Entwurfs keine Anwendung eines CollectionBuilder-Attributs zuverlässig überprüfen. Wenn wir den Iterationstypnicht kennen, wissen wir nicht, welche Signatur der Create-Methode sein sollte. Wenn der Iterationstyp aus dem Kontext stammt, besteht keine Garantie dafür, dass der Typ immer in einem ähnlichen Kontext verwendet wird.

Params Collections Funktion ist ebenfalls davon betroffen. Es fühlt sich seltsam an, dass der Elementtyp eines params-Parameters am Deklarationspunkt nicht zuverlässig vorhergesagt werden kann. Der aktuelle Vorschlag muss außerdem sicherstellen, dass die Erstellungsmethode mindestens so barrierefrei ist wie der paramsSammlungstyp. Es ist unmöglich, diese Überprüfung zuverlässig durchzuführen, es sei denn, der Sammlungstyp definiert seinen Iterationstyp selbst.

Beachten Sie, dass wir auch https://github.com/dotnet/roslyn/issues/69676 für Compiler geöffnet haben, was im Grunde dasselbe Problem beobachtet, aber aus der Perspektive der Optimierung darüber spricht.

Vorschlag

Erfordert einen Typ, der CollectionBuilder Attribut verwendet, um seinen Iterationstyp für sich selbst zu definieren. Anders ausgedrückt bedeutet dies, dass der Typ entweder IEnumarable/IEnumerable<T>implementieren soll, oder dass er über eine öffentliche GetEnumerator Methode mit der richtigen Signatur verfügen sollte (dies schließt alle Erweiterungsmethoden aus).

Außerdem muss die Methode Erstellen "dort zugänglich sein, wo der Sammlungsausdruck verwendet wird". Dies ist ein weiterer Punkt der Kontextabhängigkeit basierend auf der Barrierefreiheit. Der Zweck dieser Methode ist dem Zweck einer benutzerdefinierten Konvertierungsmethode sehr ähnlich und muss öffentlich sein. Daher sollten wir erwägen, dass auch die Erstellen-Methode öffentlich sein muss.

Schlussfolgerung

Genehmigt mit Änderungen LDM-2024-01-08

Der Begriff Iterationstyp wird nicht konsistent auf Umwandlungenangewendet.

  • Zu einem Konstrukt oder Klassentyp , der System.Collections.Generic.IEnumerable<T> implementiert, wobei:
    • Für jedes -ElementEi gibt es eine implizite Konvertierung in T.

Es sieht so aus, als würde angenommen, dass T der Iterationstyp des Konstrukts oder Klassentyps in diesem Fall notwendig ist. Diese Annahme ist jedoch falsch. Das kann zu einem sehr seltsamen Verhalten führen. Zum Beispiel:

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

class MyCollection : IEnumerable<long>
{
    IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public void Add(string l) => throw null;
    
    public IEnumerator<string> GetEnumerator() => throw null; 
}

class Program
{
    static void Main()
    {
        foreach (var l in new MyCollection())
        {
            string s = l; // Iteration type is string
        }
        
        MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
                           2];
        MyCollection x2 = new MyCollection() { "b" };
    }
}
  • Zu einem Konstrukt oder Klassentyp , der System.Collections.IEnumerable implementiert und nichtSystem.Collections.Generic.IEnumerable<T>implementiert.

Es sieht so aus, als ob bei der Implementierung davon ausgegangen wird, dass der Iterationstypobjectist, aber die Spezifikation lässt diese Tatsache unbestimmt und erfordert nicht, dass jedes Element in etwas konvertiert werden muss. Im Allgemeinen ist der Iterationstyp jedoch nicht unbedingt der object-Typ. Dies kann im folgenden Beispiel beobachtet werden:

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

class MyCollection : IEnumerable
{
    public IEnumerator<string> GetEnumerator() => throw null; 
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

class Program
{
    static void Main()
    {
        foreach (var l in new MyCollection())
        {
            string s = l; // Iteration type is string
        }
    }
}

Das Konzept des Iterationstyps ist grundlegend für Params Collections Feature. Und dieses Problem führt zu einer seltsamen Diskrepanz zwischen den beiden Features. Zum Beispiel:

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

class MyCollection : IEnumerable<long>
{
    IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public IEnumerator<string> GetEnumerator() => throw null; 

    public void Add(long l) => throw null; 
    public void Add(string l) => throw null; 
}

class Program
{
    static void Main()
    {
        Test("2"); // error CS0029: Cannot implicitly convert type 'string' to 'long'
        Test(["2"]); // error CS1503: Argument 1: cannot convert from 'collection expressions' to 'string'
        Test(3); // error CS1503: Argument 1: cannot convert from 'int' to 'string'
        Test([3]); // Ok

        MyCollection x1 = ["2"]; // error CS0029: Cannot implicitly convert type 'string' to 'long'
        MyCollection x2 = [3];
    }

    static void Test(params MyCollection a)
    {
    }
}
using System.Collections;
using System.Collections.Generic;

class MyCollection : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public IEnumerator<string> GetEnumerator() => throw null; 
    public void Add(object l) => throw null;
}

class Program
{
    static void Main()
    {
        Test("2", 3); // error CS1503: Argument 2: cannot convert from 'int' to 'string'
        Test(["2", 3]); // Ok
    }

    static void Test(params MyCollection a)
    {
    }
}

Es wird wahrscheinlich gut sein, sich in die eine oder andere Richtung zu orientieren.

Vorschlag

Geben Sie die Konvertierung von Struktur oder Klassentyp an, die System.Collections.Generic.IEnumerable<T> oder System.Collections.IEnumerable im Hinblick auf Iterationstyp implementiert und eine implizite Konvertierung für jedes ElementEi in den Iterationstyperfordert.

Schlussfolgerung

Approved LDM-2024-01-08

Sollte die Konvertierung von Sammlungsausdrücken die Verfügbarkeit eines minimalen Satzes von APIs für die Konstruktion erfordern?

Ein konstruierbarer Sammlungstyp gemäß Konvertierungen kann in Wirklichkeit nicht konstruierbar sein, was zu einem unerwarteten Verhalten bei der Auflösung von Überlasten führen kann. Zum Beispiel:

class C1
{
    public static void M1(string x)
    {
    }
    public static void M1(char[] x)
    {
    }
    
    void Test()
    {
        M1(['a', 'b']); // error CS0121: The call is ambiguous between the following methods or properties: 'C1.M1(string)' and 'C1.M1(char[])'
    }
}

Das 'C1.M1(string)' ist kein Kandidat, der verwendet werden kann, weil:

error CS1729: 'string' does not contain a constructor that takes 0 arguments
error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)

Hier ist ein weiteres Beispiel mit einem benutzerdefinierten Typ und einem stärkeren Fehler, der nicht einmal einen gültigen Kandidaten erwähnt:

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

class C1 : IEnumerable<char>
{
    public static void M1(C1 x)
    {
    }
    public static void M1(char[] x)
    {
    }

    void Test()
    {
        M1(['a', 'b']); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
    }

    public static implicit operator char[](C1 x) => throw null;
    IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

Es sieht so aus, als ob die Situation sehr ähnlich ist wie bei der Methodengruppe zum Delegieren von Conversions. D.h. es gab Szenarien, in denen die Konvertierung existierte, aber falsch war. Wir haben beschlossen, dies zu verbessern, indem wir sicherstellen, dass, wenn die Konvertierung fehlerhaft ist, sie nicht existiert.

Beachten Sie, dass mit dem Feature "Params Collections" ein ähnliches Problem auftreten wird. Es kann sinnvoll sein, die Verwendung des params Modifikators bei nicht konstruierbaren Sammlungen zu verbieten. Im aktuellen Vorschlag basiert diese Prüfung jedoch auf dem Abschnitt Konvertierungen . Hier ist ein Beispiel:

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

class C1 : IEnumerable<char>
{
    public static void M1(params C1 x) // It is probably better to report an error about an invalid `params` modifier
    {
    }
    public static void M1(params ushort[] x)
    {
    }

    void Test()
    {
        M1('a', 'b'); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
        M2('a', 'b'); // Ok
    }

    public static void M2(params ushort[] x)
    {
    }

    IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

Es sieht so aus, als ob das Problem zuvor etwas diskutiert wurde, siehe https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions. Zu diesem Zeitpunkt wurde ein Argument gemacht, dass die Regeln, wie sie gerade angegeben sind, konsistent damit sind, wie interpolierte Zeichenfolgenhandler spezifiziert sind. Hier ist ein Zitat:

Insbesondere wurden interpolierte Zeichenfolgenhandler ursprünglich auf diese Weise angegeben, aber wir haben die Spezifikation überarbeitet, nachdem dieses Problem berücksichtigt wurde.

Obwohl es eine Ähnlichkeit gibt, gibt es auch einen wichtigen Unterschied, der berücksichtigt werden sollte. Hier ist ein Zitat aus https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#interpolated-string-handler-conversion:

Der Typ T wird als applicable_interpolated_string_handler_type bezeichnet, wenn er mit System.Runtime.CompilerServices.InterpolatedStringHandlerAttributeversehen ist. Es gibt eine implizite interpolated_string_handler_conversion (Handlerkonvertierung einer interpolierten Zeichenfolge) zu T, die von einem interpolated_string_expression (interpolierten Zeichenfolgenausdruck) herrührt oder aus einem additive_expression (additiven Ausdruck), der vollständig aus „_interpolated_string_expression_s“ besteht und nur +-Operatoren verwendet.

Der zu bearbeitende Typ muss über ein spezielles Attribut verfügen, das ein starker Indikator für die Absicht des Autors ist, dass der Typ ein interpolierter Zeichenfolgenhandler sein soll. Es ist fair anzunehmen, dass das Vorhandensein des Attributs kein Zufall ist. Im Gegensatz dazu bedeutet die Tatsache, dass ein Typ "enumerable" ist, nicht notwendig, dass der Autor beabsichtigt hat, dass der Typ konstruierbar ist. Das Vorhandensein einer Erstellungsmethode, die mit einem [CollectionBuilder(...)] Attribut auf dem Sammlungstypangezeigt wird, ist ein deutlicher Hinweis auf die Absicht des Autors, dass der Typ konstruierbar sein soll.

Vorschlag

Für ein Konstrukt oder einen Klassentyp , der System.Collections.IEnumerable implementiert und der keine ErstellungsmethodeKonvertierungen hat, sollten mindestens die folgenden APIs vorhanden sein:

  • Ein barrierefreier Konstruktor, der ohne Argumente anwendbar ist.
  • Eine zugängliche Add Instanz oder Erweiterungsmethode, die mit einem Wert vom Iterationstyp als Argument aufgerufen werden kann.

Für das Params Collections Feature sind solche Typen gültige params Typen, wenn diese APIs als öffentlich deklariert werden und Instanzmethoden (statt Erweiterung) sind.

Schlussfolgerung

Genehmigt mit Änderungen LDM-2024-01-10

Designbesprechungen

https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-11-01.md#collection-literals https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-09.md#ambiguity-of--in-collection-expressions https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-28.md#collection-literals https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-10.md

Arbeitsgruppenbesprechungen

https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2022-10-06.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2022-10-14.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2022-10-21.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-04-05.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-04-28.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-05-26.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-06-12.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-06-26.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-08-03.md https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-08-10.md

Anstehende Tagesordnungspunkte

  • Stapelzuweisungen für riesige Sammlungen könnten den Stapel sprengen. Sollte der Compiler eine Heuristik zum Platzieren dieser Daten auf dem Heap haben? Sollte die Sprache nicht angegeben werden, um diese Flexibilität zu ermöglichen? Wir sollten dem folgen, was die spec/impl für params Span<T>tut. Optionen sind:

    • Immer Stackalloc. Lehren Sie die Menschen, vorsichtig mit Span umzugehen. Dies ermöglicht es, dass Dinge wie Span<T> span = [1, 2, ..s] funktionieren und gut laufen, solange s klein ist. Wenn dies den Stapel sprengen könnte, könnten Benutzer stattdessen immer ein Array erstellen und dann eine Spanne darum herum erhalten. Dies scheint am ehesten dem zu entsprechen, was Menschen möchten, jedoch mit extremer Gefahr.
    • Nur stackalloc, wenn das Literal eine feste Anzahl von Elementen hat (d.h. keine verteilten Elemente). Dies macht die Dinge dann wahrscheinlich immer sicher, mit festem Stapelverbrauch, und der Compiler kann (hoffentlich) diesen festen Puffer wiederverwenden. Es bedeutet jedoch, dass dinge wie [1, 2, ..s] niemals möglich wären, auch wenn der Benutzer weiß, dass es zur Laufzeit völlig sicher ist.
  • Wie funktioniert die Überladungsauflösung? Wenn eine API folgendes hat:

    public void M(T[] values);
    public void M(List<T> values);
    

    Was geschieht mit M([1, 2, 3])? Wir müssen wahrscheinlich "Besserheit" für diese Konvertierungen definieren.

  • Sollten wir die Sammlungsinitialisierer erweitern, um nach der sehr häufigen Methode AddRange zu suchen? Es könnte von dem zugrunde liegenden konstruierten Typ verwendet werden, um das Hinzufügen von Spread-Elementen potenziell effizienter durchzuführen. Vielleicht sollten wir auch nach Dingen wie .CopyTo suchen. Es kann hier zu Nachteilen kommen, da diese Methoden dazu führen können, dass übermäßige Zuordnungen und Verteilungen im Vergleich zur direkten Aufzählung im übersetzten Code entstehen.

  • Generische Typinferenz sollte aktualisiert werden, um Typinformationen zu/von Sammlungsliteralen fließen zu lassen. Zum Beispiel:

    void M<T>(T[] values);
    M([1, 2, 3]);
    

    Es scheint natürlich, dass dies etwas sein sollte, das der Ableitungsalgorithmus bewusst gemacht werden kann. Sobald dies für die 'Basis' konstruierbaren Sammlungstypen (T[], I<T>, Span<T>new T()) unterstützt wird, sollte es auch aus dem Fall Collect(constructible_type) herausfallen. Zum Beispiel:

    void M<T>(ImmutableArray<T> values);
    M([1, 2, 3]);
    

    Hier ist Immutable<T> über eine init void Construct(T[] values) Methode konstruierbar. Daher würde der T[] values Typ mit Rückschlüssen auf [1, 2, 3] verwendet werden, was zu einer Ableitung von int für Tführt.

  • Cast/Index Mehrdeutigkeit.

    Heute ist der folgende Ausdruck indiziert in

    var v = (Expr)[1, 2, 3];
    

    Aber es wäre schön, Dinge wie machen zu können:

    var v = (ImmutableArray<int>)[1, 2, 3];
    

    Können/sollten wir hier eine Pause machen?

  • Syntaktische Zweideutigkeiten mit ?[.

    Es kann sinnvoll sein, die Regeln für nullable index access zu ändern, um anzugeben, dass kein Platz zwischen ? und [auftreten kann. Dies wäre eine einschneidende Änderung (aber wahrscheinlich geringfügig, da Visual Studio (VS) dies bereits zusammenführt, wenn man versucht, sie mit einem Leerzeichen einzugeben). Wenn wir dies tun, können wir x?[y] anders als x ? [y]interpretieren.

    Etwas Ähnliches tritt auf, wenn wir mit https://github.com/dotnet/csharplang/issues/2926fortfahren möchten. In dieser Welt ist x?.y zweideutig mit x ? .y. Wenn wir verlangen, dass das ?. aneinanderstößt, können wir die beiden Fälle syntaktisch trivial unterscheiden.