Freigeben über


Auflistungsausdrucksargumente

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

Motivation

Das Wörterbuchausdrucksfeature hat festgestellt, dass Sammlungsausdrücke zum Übergeben von vom Benutzer angegebenen Daten benötigt werden, um das Verhalten der endgültigen Auflistung zu konfigurieren. Insbesondere können Benutzer mithilfe von Wörterbüchern anpassen, wie ihre Schlüssel verglichen werden, indem sie die Gleichheit zwischen Schlüsseln definieren und sortieren oder hashen (im Fall sortierter oder hashierter Auflistungen). Dies gilt beim Erstellen einer beliebigen Art von Wörterbuchtyp (zD d = new D(...)D d = D.CreateRange(...). B. und sogarIDictionary<...> d = <synthesized dict>)

Um dies zu unterstützen, wird ein neues with(...arguments...) Element als erstes Element eines Sammlungsausdrucks wie folgt vorgeschlagen:

Dictionary<string, int> nameToAge = [with(comparer), .. d1, .. d2, .. d3];
  1. Bei der Übersetzung in einen new CollectionType(...) Aufruf werden diese ...arguments... verwendet, um den entsprechenden Konstruktor zu ermitteln und entsprechend zu übergeben.
  2. Beim Übersetzen in einen CollectionFactory.Create Aufruf werden diese ...arguments... vor dem ReadOnlySpan<ElementType> Elementargument übergeben, das alle verwendet werden, um die entsprechende Create Überladung zu ermitteln und entsprechend zu übergeben.
  3. Beim Übersetzen in eine Schnittstelle (z IDictionary<,>. B. ) ist nur ein einzelnes Argument zulässig. Es implementiert eine der bekannten BCL-Vergleichsschnittstellen und wird verwendet, um die Schlüsselvergleichsemantik der endgültigen Instanz zu steuern.

Diese Syntax wurde wie folgt ausgewählt:

  1. Behält alle Informationen in der [...] Syntax bei. Stellen Sie sicher, dass der Code immer noch klar darauf hinweist, dass eine Sammlung erstellt wird.
  2. Impliziert nicht das Aufrufen eines new Konstruktors (wenn nicht, wie alle Auflistungen erstellt werden).
  3. Bedeutet nicht, dass die Werte der Auflistung mehrmals erstellt/kopiert werden (z. B. ein Postfix with { ... } ).
  4. Führt nicht die Reihenfolge der Vorgänge aus, insbesondere bei der konsistenten Sortiersemantik von Links-nach-rechts-Ausdrücken. Beispielsweise werden die Zum Erstellen einer Auflistung verwendeten Argumente nicht ausgewertet, nachdem die zum Auffüllen der Auflistung verwendeten Ausdrücke ausgewertet wurden.
  5. Erzwingt nicht, dass ein Benutzer das Ende eines (potenziell großen) Sammlungsausdrucks liest, um die Kernverhaltensemantik zu bestimmen. Wenn Sie beispielsweise das Ende eines hundertzeiligen Wörterbuchs sehen müssen, um nur zu finden, dass es den richtigen Schlüsselvergleich verwendet hat.
  6. Ist beides nicht subtil, auch nicht übermäßig ausführlich. Das Verwenden ; von Argumenten anstelle von , Argumenten ist beispielsweise ein sehr einfaches Syntaxstück, das nicht zu übersehen ist. with() fügt nur 6 Zeichen hinzu und zeichnet sich leicht aus, insbesondere bei der Syntaxfarbe des with Schlüsselworts.
  7. Liest schön vor. "Dies ist ein Sammlungsausdruck mit diesen Argumenten, die aus diesen Elementen bestehen."
  8. Löst die Notwendigkeit für Vergleiche für Wörterbücher und Sets.
  9. Stellt sicher, dass jeder Benutzer Argumente übergeben muss, oder alle Anforderungen, die wir selbst über Vergleiche in Der Zukunft verfügen, bereits behandelt werden.
  10. Steht nicht im Konflikt mit vorhandenem Code (mithilfe https://grep.app/ der Suche).

Designphilosophie

Der folgende Abschnitt befasst sich mit früheren Diskussionen zur Designphilosophie. Einschließlich der Gründe, warum bestimmte Formulare abgelehnt wurden.

Es gibt zwei Hauptrichtungen, in die wir gehen können, um diese benutzerdefinierten Daten zu liefern. Im ersten Fall handelt es sich um nur Werte im Vergleichsbereich (die wir als Typen definieren, die von den BCL-Typen IComparer<T>IEqualityComparer<T> erben). Der zweite besteht darin, beim Erstellen von Auflistungsausdrücken einen generalisierten Mechanismus bereitzustellen, um beliebige Argumente für die endgültige aufgerufene API bereitzustellen. Die Spezifikation des primären Wörterbuchausdrucks zeigt, wie der frühere Vorgang ausgeführt werden kann, während diese Spezifikation versucht, letzteres zu erledigen.

Untersuchungen der Lösungen für die bloße Übergabe von Vergleichsabgleichern haben schwachstellen in ihrem Ansatz ergeben, wenn wir sie auf willkürliche Argumente erweitern wollten. Beispiel:

  1. Erneutes Verwenden der Elementsyntax, wie bei der Form: [StringComparer.OrdinalIgnoreCase, "mads": 21]. Dies funktioniert gut in einem Raum, in dem KeyValuePair<,> und Vergleicher nicht von allgemeinen Typen erben. Aber es bricht in einer Welt auf, in der man tun könnte: HashSet<object> h = [StringComparer.OrdinalIgnoreCase, "v"]. Übergibt dies einen Vergleich? Oder versuchen Sie, zwei Objektwerte in den Satz zu setzen?

  2. Trennen von Argumenten und Elementen mit subtiler Syntax (z. B. verwenden Sie ein Semikolon anstelle eines Kommas, um sie voneinander [comparer; v1]zu trennen). Dies birgt sehr verwirrende Situationen, in denen ein Benutzer versehentlich schreibt [1; 2] (und eine Sammlung erhält, die "1" übergibt, z. B. das Argument "Kapazität" für ein List<>, und enthält nur den einzelnen Wert "2"), wenn er beabsichtigt [1, 2] ist (eine Auflistung mit zwei Elementen).

Um willkürliche Argumente zu unterstützen, glauben wir, dass eine offensichtlichere Syntax erforderlich ist, um diese Werte deutlicher abzugrenzen. In diesem Raum haben sich auch einige andere Designaspekte ergeben. In keiner bestimmten Reihenfolge sind dies:

  1. Dass die Lösung nicht mehrdeutig ist und zu Unterbrechungen mit Code führt, die die Benutzer heute wahrscheinlich mit Sammlungsausdrücken verwenden. Beispiel:

    List<Widget> c = [new(...), w1, w2, w3];
    

    Dies ist heute legal, wobei der new(...) Ausdruck eine "implizite Objekterstellung" ist, die ein neues Widget erstellt. Wir können dies nicht umverwenden, um Argumente an den Konstruktor weiterzuverwenden List<>, da er den vorhandenen Code sicherlich unterbrechen würde.

  2. Dass die Syntax nicht auf außerhalb des [...] Konstrukts erweitert wird. Beispiel:

    HashSet<string> s = [...] with ...;
    

    Diese Syntaxen können so ausgelegt werden, dass die Sammlung zuerst erstellt und dann in einer unterschiedlichen Form neu erstellt wird, was mehrere Transformationen der Daten impliziert, und potenziell unerwünschte höhere Kosten (auch wenn dies nicht das ist, was ausgegeben wird).

  3. Das new als potenzielles Schlüsselwort, das überhaupt in diesem Bereich verwendet werden soll, ist unerwündlich verwirrend. Beides liegt [...] daran, dass bereits ein neues Objekt erstellt wird, und da Übersetzungen des Auflistungsausdrucks möglicherweise nicht-Konstruktor-APIs durchlaufen (z. B. das Create-Methodenmuster).

  4. Dass die Lösung nicht übermäßig ausführlich ist. Ein Kernwertversprechen von Sammlungsausdrücken ist Platz. Wenn das Formular also eine große Menge syntaktischer Gerüste hinzufügt, fühlt es sich wie ein Schritt rückwärts an und unterschreitet das Wertversprechen der Verwendung von Sammlungsausdrücken im Vergleich zum Aufrufen der vorhandenen APIs, um die Auflistung zu erstellen.

Beachten Sie, dass eine Syntax wie new([...], ...) "2" und "3" oben nicht ausgeführt wird. Es erscheint so, als ob wir einen Konstruktor aufrufen (wenn wir möglicherweise nicht sein) und es impliziert, dass ein erstellter Sammlungsausdruck an diesen Konstruktor übergeben wird, was definitiv nicht der Fall ist.

Basierend auf allen oben genannten, haben sich einige wenige Optionen ergeben, die gefühlt werden, die Bedürfnisse der Übergabeargumente zu lösen, ohne die Grenzen der Ziele von Sammlungsausdrücken zu lösen.

[with(...arguments...)] Design

Syntax:

collection_element
   : expression_element
   | spread_element
+  | with_element
   ;

+with_element
+  : 'with' argument_list
+  ;

Es gibt eine syntaktische Mehrdeutigkeit, die sofort mit dieser Grammatikproduktion eingeführt wurde. Ähnlich wie die Mehrdeutigkeit zwischen spread_element und expression_element(hier erläutert, gibt es eine sofortige syntaktische Mehrdeutigkeit zwischen with_element und expression_element. Speziell with(<arguments>) ist genau der Produktionskörper für with_element, und ist auch erreichbar durch expression_element -> expression -> ... -> invocation_expression. Es gibt eine einfache übergeordnete Regel für collection_elements. Wenn das Element lexikalisch mit der Tokensequenz with( beginnt, wird es immer als eine with_elementbehandelt.

Dies ist auf zwei Arten von Vorteil. Zunächst muss eine Compilerimplementierung nur die unmittelbar folgenden Token betrachten, um zu bestimmen, welche Art von Element analysiert werden soll. Zweitens kann ein Benutzer leicht verstehen, welche Art von Element sie haben, ohne mental versuchen zu müssen, zu analysieren, was folgt zu analysieren, um zu sehen, ob er es als ein with_element oder ein expression_element.

Beispiele

Beispiele dafür, wie dies aussehen würde:

// With an existing type:

// Initialize to twice the capacity since we'll have to add
// more values later.
List<string> names = [with(capacity: values.Count * 2), .. values];

Diese Formen scheinen vernünftigerweise zu "lesen". In all diesen Fällen lautet der Code "Erstellen eines Sammlungsausdrucks, "mit" den folgenden Argumenten, die übergeben werden sollen, um die endgültige Instanz zu steuern, und dann die nachfolgenden Elemente, die zum Auffüllen verwendet werden. Beispielsweise erstellt die erste Zeile "erstellt eine Liste von Zeichenfolgen "mit" eine Kapazität von zwei Mal der Anzahl der Werte, die in sie verteilt werden sollen"

Wichtig ist, dass dieser Code wenig Chancen hat, wie bei Formularen wie: [arg; element]zu übersehen, während auch minimale Ausführlichkeit hinzugefügt wird, mit einer großen Flexibilität, um alle gewünschten Argumente zusammen zu übergeben.

Dies wäre technisch eine bahnbrechende Änderung, dawith(...) es sich um einen Aufruf einer bereits vorhandenen Methode handelte, die aufgerufen wird.with Im Gegensatz zu new(...) einer bekannten und empfohlenen Methode zum Erstellen implizit typierter Werte with(...) ist dies jedoch viel weniger wahrscheinlich als Methodenname, wobei die Benennung von .Net für Methoden nicht ausgeführt wird. Im unwahrscheinlichen Fall, dass ein Benutzer über eine solche Methode verfügt, wäre er sicherlich in der Lage, den Aufruf der vorhandenen Methode mit der Verwendung @with(...)fortzusetzen.

Wir würden dieses with(...) Element wie folgt übersetzen:

List<string> names = [with(/*capacity*/10), ...]; // translates to:

// argument_list *becomes* the argument list for the
// constructor call. 
__result = new List<string>(10); // followed by normal initialization

// or

IList<string> names2 = [with(capacity: 20), ...]; // translates to:

__result = new List<string>(20);

Mit anderen Worten, die argument_list Argumente werden an den entsprechenden Konstruktor übergeben, wenn wir einen Konstruktor aufrufen, oder an die entsprechende "Create-Methode", wenn wir eine solche Methode aufrufen. Wir würden auch zulassen, dass ein einzelnes Argument, das von den BCL-Vergleichstypen erbt, beim Instanziieren eines der Zielwörterbuch-Schnittstellentypen bereitgestellt wird, um sein Verhalten zu steuern.

Konvertierungen

Der Abschnitt "Konvertierungen" für Auflistungsausdrücke wird wie folgt aktualisiert:

> A struct or class type that implements System.Collections.IEnumerable where:

-  * The type has an applicable constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression.
+  a. the collection expression has no `with_element` and the type has an applicable constructor
+     that can be invoked with no arguments, accessible at the location of the collection expression. or
+  b. the collection expression has a `with_element` and the type has at least one constructor
+     accessible at the location of the collection expression. 

Beachten Sie die tatsächlichen Argumente innerhalb argument_list des Bereichs with_element nicht, wenn die Konvertierung vorhanden ist oder nicht. Nur die Anwesenheit oder Abwesenheit des with_element selbst. Die Intuition hier ist einfach, dass, wenn der Sammlungsausdruck ohne einen geschrieben wird (wie [x, y, z]) müsste es sein, den Konstruktor ohne Argumente aufrufen zu können. Wenn dies der Richtige ist [with(...), x, y, z] , kann der entsprechende Konstruktor aufgerufen werden. Dies bedeutet auch, dass Typen, die nicht mit einem No-Argument-Konstruktor aufgerufen werden können, mit einem Auflistungsausdruck verwendet werden können , sondern nur , wenn dieser Auflistungsausdruck, der einen with_elemententhält.

Die tatsächliche Bestimmung, wie sich ein with_element Wille auf den Bau auswirken wird, wird unten angegeben.

Konstruktion

Der Bau wird wie folgt aktualisiert.

Die Elemente eines Auflistungsausdrucks werden von links nach rechts ausgewertet. Innerhalb von Sammlungsargumenten werden die Argumente nach links nach rechts ausgewertet. Jedes Element oder Argument wird genau einmal ausgewertet, und alle weiteren Verweise beziehen sich auf die Ergebnisse dieser erstauswertung.

Wenn collection_arguments enthalten ist und nicht das erste Element im Auflistungsausdruck ist, wird ein Kompilierungszeitfehler gemeldet.

Wenn die Argumentliste Werte mit dynamischem Typ enthält, wird ein Kompilierungsfehler gemeldet (LDM-2025-01-22).

Konstruktoren

Wenn der Zieltyp eine Struktur oder ein Klassentyp ist, der implementiert System.Collections.IEnumerablewird, und der Zieltyp keine Create-Methode aufweist und der Zieltyp kein generischer Parametertyp ist, dann gilt Folgendes:

  • Die Überladungsauflösung wird verwendet, um den besten Instanzkonstruktor aus den Kandidaten zu ermitteln.
  • Der Satz von Kandidatenkonstruktoren ist alle Konstruktoren für barrierefreie Instanzen, die für den Zieltyp deklariert sind, der in Bezug auf die Argumentliste gilt, die im anwendbaren Funktionsmememm definiert ist.
  • Wenn ein Konstruktor der besten Instanz gefunden wird, wird der Konstruktor mit der Argumentliste aufgerufen.
    • Wenn der Konstruktor über einen params Parameter verfügt, kann der Aufruf in erweiterter Form erfolgen.
  • Andernfalls wird ein Bindungsfehler gemeldet.
// List<T> candidates:
//   List<T>()
//   List<T>(IEnumerable<T> collection)
//   List<T>(int capacity)
List<int> l;
l = [with(capacity: 3), 1, 2]; // new List<int>(capacity: 3)
l = [with([1, 2]), 3];         // new List<int>(IEnumerable<int> collection)
l = [with(default)];           // error: ambiguous constructor

CollectionBuilderAttribute-Methoden

Wenn der Zieltyp ein Typ mit einer Create-Methode ist, dann:

  • Die Überladungsauflösung wird verwendet, um die beste Erstellungsmethode aus den Kandidaten zu ermitteln.
  • Für jede Erstellungsmethode für den Zieltyp definieren wir eine Projektionsmethode mit einer identischen Signatur mit der Erstellungsmethode, aber ohne den letzten Parameter.
  • Der Satz von Kandidatenprojektionsmethoden ist die Projektionsmethoden , die in Bezug auf die Argumentliste anwendbar sind, wie sie im anwendbaren Funktionsmememm definiert sind.
  • Wenn eine beste Projektionsmethode gefunden wird, wird die entsprechende Erstellungsmethode mit der Argumentliste aufgerufen, die mit einem ReadOnlySpan<T> Element angefügt ist.
  • Andernfalls wird ein Bindungsfehler gemeldet.
[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> { ... }

class MyBuilder
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> elements);
    public static MyCollection<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> elements);
}
MyCollection<string> c1 = [with(GetComparer()), "1", "2"];
// IEqualityComparer<string> _tmp1 = GetComparer();
// ReadOnlySpan<string> _tmp2 = ["1", "2"];
// c1 = MyBuilder.Create<string>(_tmp1, _tmp2);

MyCollection<string> c2 = [with(), "1", "2"];
// ReadOnlySpan<string> _tmp3 = ["1", "2"];
// c2 = MyBuilder.Create<string>(_tmp3);

CollectionBuilderAttribute: Create-Methoden

Für einen Auflistungsausdruck, in dem die Zieltypdefinition ein [CollectionBuilder] Attribut aufweist, sind die Erstellungsmethoden die folgenden, aus Sammlungsausdrücken aktualisiert: Erstellen von Methoden.

Ein [CollectionBuilder(...)] Attribut gibt den Generatortyp und den Methodennamen einer Methode an, die aufgerufen werden soll, um eine Instanz des Auflistungstyps zu erstellen.

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 letzten 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.

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.

Die wichtigsten Unterschiede des früheren Algorithmus sind:

  • Create-Methoden verfügen möglicherweise über zusätzliche Parameter vor dem ReadOnlySpan<E> Parameter.
  • Es werden mehrere Erstellungsmethoden unterstützt.

Schnittstellenzieltyp

Wenn der Zieltyp ein Schnittstellentyp ist, dann:

  • Die Überladungsauflösung wird verwendet, um die beste Signatur der Kandidatenmethode zu ermitteln.

  • Der Satz von Kandidatensignaturen ist die unten aufgeführten Signaturen für die Zielschnittstelle, die in Bezug auf die Argumentliste gemäß der Definition im anwendbaren Funktionselement anwendbar sind.

    Schnittstellen Kandidatensignaturen
    IEnumerable<E>
    IReadOnlyCollection<E>
    IReadOnlyList<E>
    () (keine Parameter)
    ICollection<E>
    IList<E>
    List<E>()
    List<E>(int)

Wenn eine beste Methodensignatur gefunden wird, sind die Semantik wie folgt:

  • Die Kandidatensignatur für IEnumerable<E>und IReadOnlyCollection<E> ist einfach () und IReadOnlyList<E> hat die gleiche Bedeutung wie das Element überhaupt nichtwith().
  • Die Kandidatensignaturen für IList<T> und ICollection<T> sind die Signaturen und List<T>(int)List<T>() Konstruktoren. Beim Erstellen des Werts (siehe Veränderbare Schnittstellenübersetzung) wird der entsprechende List<T> Konstruktor aufgerufen.
  • Andernfalls wird ein Bindungsfehler gemeldet.

Dictionary-Interface Zieltyp

Dies wird hier als Teil des features angegeben, das in https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md.

Die obige Liste wird erweitert, um die folgenden Elemente zu haben:

Schnittstellen Kandidatensignaturen
IReadOnlyDictionary<K, V> () (keine Parameter)
(IEqualityComparer<K>? comparer)
IDictionary<K, V> Dictionary<K, V>()
Dictionary<K, V>(int)
Dictionary<K, V>(IEqualityComparer<K>)
Dictionary<K, V>(int, IEqualityComparer<K>)

Wenn eine beste Methodensignatur gefunden wird, werden die Semantik wie folgt dargestellt:

  • Die Kandidatensignaturen IReadOnlyDictionary<K, V> sind () (die die gleiche Bedeutung haben wie das with() Element überhaupt nicht), und (IEqualityComparer<K>). Dieser Vergleich wird verwendet, um einen entsprechenden Hash zu erstellen und die Schlüssel im Zielwörterbuch zu vergleichen, die der Compiler erstellen möchte (siehe Nicht mutable Interface Translation).
  • Die Kandidatensignaturen sind die Signaturen IDictionary<T> von Dictionary<K, V>(), Dictionary<K, V>(int)und Dictionary<K, V>(IEqualityComparer<K>)Dictionary<K, V>(int, IEqualityComparer<K>)Konstruktoren. Beim Erstellen des Werts (siehe Veränderbare Schnittstellenübersetzung) wird der entsprechende Dictionary<K, V> Konstruktor aufgerufen.
  • Andernfalls wird ein Bindungsfehler gemeldet.
IDictionary<string, int> d;
IReadOnlyDictionary<string, int> r;

d = [with(StringComparer.Ordinal)]; // new Dictionary<string, int>(StringComparer.Ordinal)
r = [with(StringComparer.Ordinal)]; // new $PrivateImpl<string, int>(StringComparer.Ordinal)

d = [with(capacity: 2)]; // new Dictionary<string, int>(capacity: 2)
r = [with(capacity: 2)]; // error: 'capacity' parameter not recognized
d = [with()];            // Legal: empty arguments supported for interfaces

Andere Zieltypen

Wenn der Zieltyp ein anderer Typ ist, wird für die Argumentliste ein Bindungsfehler gemeldet, auch wenn er leer ist.

Span<int> a = [with(), 1, 2, 3]; // error: arguments not supported
Span<int> b = [with([1, 2]), 3]; // error: arguments not supported

int[] a = [with(), 1, 2, 3]; // error: arguments not supported
int[] b = [with(length: 1), 3]; // error: arguments not supported

Referenzsicherheit

Wir passen die Regeln für die Sammlungsausdrücke.md#ref-safety an, um das with() Element zu berücksichtigen.

Siehe auch §16.4.15 Einschränkung des sicheren Kontexts.

Erstellen von Methoden

Dieser Abschnitt gilt für Auflistungsausdrücke, deren Zieltyp die in CollectionBuilderAttribute-Methoden definierten Einschränkungen erfüllt.

Der sichere Kontext wird durch Ändern einer Klausel aus "collection-expressions.md#ref-safety " (fett formatierte Änderungen) bestimmt:

  • Wenn der Zieltyp ein Verweisstrukturtyp mit einer Create-Methode ist, ist der sichere Kontext des Auflistungsausdrucks der sichere Kontext eines Aufrufs der Create-Methode , wobei die Argumente die with() Elementargumente sind, gefolgt vom Auflistungsausdruck als Argument für den letzten Parameter (der ReadOnlySpan<E> Parameter).

Die Methodenargumente müssen mit der Einschränkung übereinstimmen, die für den Auflistungsausdruck gilt. Ähnlich wie bei der obigen Ermittlung des sicheren Kontextsmüssen die Methodenargumente mit der Einschränkung übereinstimmen, indem der Sammlungsausdruck als Aufruf der Create-Methode behandelt wird, wobei die Argumente die with() Elementargumente sind, gefolgt vom Auflistungsausdruck als Argument für den letzten Parameter.

Konstruktoraufrufe

Dieser Abschnitt gilt für Auflistungsausdrücke, deren Zieltyp die in Konstruktoren definierten Einschränkungen erfüllt.

Für einen Auflistungsausdruck eines Verweisstrukturtyps des folgenden Formulars:
[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]

Der sichere Kontext des Auflistungsausdrucks ist der schmalste der sicheren Kontexte der folgenden Ausdrücke:

  • Ein Objekterstellungsausdruck new C(a₁, a₂, ..., aₙ), wobei C es sich um den Zieltyp handelt
  • Die Elementausdrücke e₁, e₂, ..., eₙ (entweder die Ausdrücke selbst oder der Spreadwert im Fall eines Spread-Elements).

Die Methodenargumente müssen mit der Einschränkung übereinstimmen, die für den Auflistungsausdruck gilt. Die Einschränkung wird angewendet, indem der Auflistungsausdruck als Objekterstellung des Formulars new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ } pro struct-improvements.md#rules-for-object-initializers behandelt wird.

  • Ausdruckselemente werden so behandelt, als wären sie Sammlungselementinitialisierer.
  • Spread-Elemente werden ähnlich behandelt, indem vorübergehend davon ausgegangen wird, dass es C sich um eine Add(SpreadType spread) Methode handelt, bei der SpreadType es sich um den Typ des Spreadwerts handelt.

Beantwortete Fragen

dynamic-Argumente

Sollen Argumente mit dynamic Typ zulässig sein? Dies kann die Verwendung des Laufzeitordners für die Überladungsauflösung erfordern, was es schwierig macht, die Gruppe von Kandidaten einzuschränken, z. B. für Sammlungs-Generator-Fälle.

Auflösung: Unzulässige. LDM-2025-01-22

with() Umbruchänderung

Das vorgeschlagene with() Element ist eine bahnbrechende Änderung.

object x, y, z = ...;
object[] items = [with(x, y), z]; // C#13: ok; C#14: error args not supported for object[]

object with(object x, object y) { ... }

Vergewissern Sie sich, dass die aktuelle Änderung akzeptabel ist und ob die Änderung nicht mit der Sprachversion verknüpft werden soll.

Auflösung: Behalten Sie beim Kompilieren mit einer früheren Sprachversion das vorherige Verhalten bei (keine Unterbrechungsänderung). LDM-2025-03-17

Sollten Argumente auswirkungen auf die Konvertierung des Sammlungsausdrucks haben?

Sollen Sammlungsargumente und die entsprechenden Methoden die Konvertierung des Auflistungsausdrucks beeinflussen?

Print([with(comparer: null), 1, 2, 3]); // ambiguous or Print<int>(HashSet<int>)?

static void Print<T>(List<T> list) { ... }
static void Print<T>(HashSet<T> set) { ... }

Wenn sich die Argumente auf die Konvertierung basierend auf den anwendbaren Methoden auswirken, sollten sich argumente wahrscheinlich auch auf die Typeinleitung auswirken.

Print([with(comparer: StringComparer.Ordinal)]); // Print<string>(HashSet<string>)?

Ähnliche Fälle mit Zieltyp new() führen zu Fehlern.

Print<int>(new(comparer: null));              // error: ambiguous
Print(new(comparer: StringComparer.Ordinal)); // error: type arguments cannot be inferred

Auflösung: Sammlungsargumente sollten in Konvertierungen und Typreferenzen ignoriert werden. LDM-2025-03-17

Parameterreihenfolge der Sammlungs-Generator-Methode

Für Methoden des Sammlungs-Generators sollte der Span-Parameter vor oder nach Parametern für Sammlungsargumente sein?

Elemente würden zunächst zulassen, dass die Argumente als optional deklariert werden.

class MySetBuilder
{
    public static MySet<T> Create<T>(ReadOnlySpan<T> items, IEqualityComparer<T> comparer = null) { ... }
}

Argumente würden zunächst zulassen, dass die Spanne ein params Parameter ist, um das Aufrufen direkt in erweiterter Form zu unterstützen.

var s = MySetBuilder.Create(StringComparer.Ordinal, x, y, z);

class MySetBuilder
{
    public static MySet<T> Create<T>(IEqualityComparer<T> comparer, params ReadOnlySpan<T> items) { ... }
}

Auflösung: Der Span-Parameter für Elemente sollte der letzte Parameter sein. LDM-2025-03-12

Argumente mit früherer Sprachversion

Wird beim Kompilieren mit einer früheren Sprachversion ein Fehler gemeldet with() oder wird with eine Bindung an ein anderes Symbol im Bereich vorgenommen?

Auflösung: Beim Kompilieren mit früheren Sprachversionen gibt es keine unterbrechungsbezogene Änderung innerhalb with eines Auflistungsausdrucks. LDM-2025-03-17

Zieltypen, bei denen Argumente erforderlich sind

Sollten Auflistungsausdruckkonvertierungen in Zieltypen unterstützt werden, bei denen Argumente angegeben werden müssen, da alle Konstruktoren oder Factorymethoden mindestens ein Argument erfordern?

Solche Typen können mit Auflistungsausdrücken verwendet werden, die explizite with() Argumente enthalten, aber die Typen konnten nicht für params Parameter verwendet werden.

Betrachten Sie beispielsweise den folgenden Typ, der aus einer Factorymethode erstellt wurde:

MyCollection<object> c;
c = [];                  // error: no arguments
c = [with(capacity: 1)]; // ok

[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> : IEnumerable<T> { ... }

class MyBuilder
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> items, int capacity) { ... }
}

Die gleiche Frage gilt, wenn der Konstruktor direkt wie im folgenden Beispiel aufgerufen wird.

Für die Zieltypen, bei denen der Konstruktor direkt aufgerufen wird, erfordert die Konvertierung des Sammlungsausdrucks derzeit jedoch einen Konstruktor ohne Argumente, aber die Auflistungsargumente werden beim Bestimmen der Konvertierung ignoriert.

c = [];                  // error: no arguments
c = [with(capacity: 1)]; // error: no constructor callable with no arguments?

class MyCollection<T> : IEnumerable<T>
{
    public MyCollection(int capacity) { ... }
    public void Add(T t) { ... }
    // ...
}

Auflösung: Unterstützen Sie Konvertierungen in Zieltypen, bei denen alle Konstruktoren oder Factorymethoden Argumente erfordern und für die Konvertierung erforderlich with() sind. LDM-2025-03-05

__arglist

Sollte __arglist in with() Elementen unterstützt werden?

class MyCollection : IEnumerable
{
    public MyCollection(__arglist) { ... }
    public void Add(object o) { }
}

MyCollection c;
c = [with(__arglist())];    // ok
c = [with(__arglist(x, y)]; // ok

Auflösung: Keine Unterstützung für __arglist Sammlungsargumente, es sei denn, sie sind frei. LDM-2025-03-05

Argumente für Schnittstellentypen

Sollten Argumente für Schnittstellenzieltypen unterstützt werden?

ICollection<int> c = [with(capacity: 4)];
IReadOnlyDictionary<string, int> d = [with(comparer: StringComparer.Ordinal), ..values];
Wenn ja, welche Methodensignaturen werden beim Binden der Argumente verwendet?

Für veränderbare Schnittstellentypen sind die folgenden Optionen verfügbar:

  1. Verwenden Sie die barrierefreien Konstruktoren aus dem bekannten Typ, der für die Instanzierung erforderlich ist: List<T> oder Dictionary<K, V>.
  2. Verwenden Sie Signaturen unabhängig vom jeweiligen Typ, z. B. unter Verwendung new() und new(int capacity) für und für ICollection<T> und IList<T> (siehe Konstruktion potenzieller Signaturen für jede Schnittstelle).

Die Verwendung der barrierefreien Konstruktoren aus einem bekannten Typ hat folgende Auswirkungen:

  • Parameternamen, optional-ness, paramswerden direkt aus den Parametern entnommen.
  • Alle barrierefreien Konstruktoren sind enthalten, auch wenn dies möglicherweise nicht für Auflistungsausdrücke nützlich ist, z List(IEnumerable<T>) . B. dies zulassen IList<int> list = [with(1, 2, 3)];würde.
  • Die Gruppe von Konstruktoren hängt möglicherweise von der BCL-Version ab.

Recomendation: Verwenden Sie die barrierefreien Konstruktoren aus den bekannten Typen. Wir haben garantiert, dass wir diese Typen verwenden würden, sodass diese nur "fallen" und der klarste und einfachste Weg zur Erstellung dieser Werte ist.

Bei nicht veränderbaren Schnittstellentypen sind die Optionen ähnlich:

  1. Keine Aktion. Das
  2. Verwenden Sie Signaturen unabhängig vom jeweiligen Typ, obwohl das einzige Szenario für C#14 sein new(IEqualityComparer<K> comparer)IReadOnlyDictionary<K, V> kann.

Die Verwendung von barrierefreien Konstruktoren aus einem bekannten Typ (die Strategie für veränderbare Schnittstellentypen) ist nicht praktikabel, da es keine Beziehung zu einem bestimmten vorhandenen Typ gibt, und der endgültige Typ, den wir verwenden und/oder synthetisieren können. Daher müsste es ungerade neue Anforderungen geben, die der Compiler jedem vorhandenen Konstruktor des betreffenden Typs (auch wenn er sich weiterentwickelt) der tatsächlich generierten nicht änderbaren Instanz zuordnen kann.

ErneutesComendieren: Verwenden Von Signaturen unabhängig von einem bestimmten Typ. Und bei C# 14 wird nur die einzige nicht änderbare Schnittstelle unterstützt new(IEqualityComparer<K> comparer)IReadOnlyDictionary<K, V> , bei der es uns wichtig ist, dass Benutzer dies bereitstellen können. Zukünftige C#-Versionen können erwägen, diesen Satz basierend auf soliden Begründungen zu erweitern.

Auflösung:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md

Argumente werden für Schnittstellenzieltypen unterstützt. Für veränderliche und nicht veränderbare Schnittstellen werden die Argumente zusammengestellt.

Die erwartete Liste (die noch LDM ratifiziert werden muss) ist der Schnittstellenzieltyp.

Leere Argumentlisten

Sollen leere Argumentlisten für einige oder alle Zieltypen zulässig sein?

Ein leerer with() Wert wäre gleichbedeutend mit nein with(). Es kann eine gewisse Konsistenz mit nicht leeren Fällen bieten, aber es würde keine neue Funktion hinzufügen.

Die Bedeutung eines leeren "with()" kann für einige Zieltypen eindeutiger sein als für andere: - Für Typen, bei denen **Konstruktoren** verwendet werden, rufen Sie den entsprechenden Konstruktor ohne Argumente auf. - Rufen Sie für Typen mit **'CollectionBuilderAttribute'**die anwendbare Factorymethode nur mit Elementen auf. - Erstellen Sie für **Schnittstellentypen** den bekannten oder implementierungsdefinierte Typ ohne Argumente. - Für **Arrays** und **spans**, wobei Sammlungsargumente andernfalls nicht unterstützt werden, kann "with()" verwirrend sein.
List<int>           l = [with()]; // ok? new List<int>()
ImmutableArray<int> m = [with()]; // ok? ImmutableArray.Create<int>()

IList<int>       i = [with()]; // ok? new List<int>() or equivalent
IEnumerable<int> e = [with()]; // ok?

int[]     a = [with()]; // ok?
Span<int> s = [with()]; // ok?

Auflösung:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md#empty-argument-lists

Wir lassen mit() Konstruktortypen und Generatortypen zu, die überhaupt ohne Argumente aufgerufen werden können, und wir fügen leere Konstruktorsignaturen für die Schnittstellentypen (stummschaltbar und schreibgeschützt) hinzu. Arrays und Spans lassen nicht mit() zu, da keine Signaturen vorhanden sind, die sie anpassen würden.

Offene Fragen

Abschließen eines offenen Anliegens von https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion

with(...) ist eine bahnbrechende Änderung in der Sprache mit [with(...)]. Vor diesem Feature bedeutet dies ein Auflistungsausdruck mit einem Element, das das Ergebnis des Aufrufs des withAusdrucks -invocation-expression ist. Nach diesem Feature handelt es sich um eine Sammlung, die Argumente enthält, die an dieses Feature übergeben werden.

Soll dieser Umbruch nur auftreten, wenn ein Benutzer eine bestimmte Sprachversion (z C#-14/15. B. ?) auswählt. Anders ausgedrückt: Wenn sie sich auf einer älteren Langversion befinden, erhalten sie die vorherige Analyselogik, aber in der neueren Version erhalten sie die neuere Analyselogik. In Or möchten wir immer die neuere Analyselogik haben, auch auf einer älteren Langversion?

Für beide Strategien haben wir Vorrang. requiredwird beispielsweise unabhängig von der Langversion immer mit der neuen Logik analysiert. record/field Während und andere ihre Analyselogik je nach Sprachversion ändern.

Schließlich hat dies Überschneidungen und Auswirkungen mit Dictionary Expressions, die die key:value Syntax für KVP-Elemente eingeführt. Wir möchten das Verhalten festlegen, das wir für jede lang-Version und für [with(...)] sich selbst, und dinge wie [with(...) : expr] oder [expr : with(...)].