params Collections
Hinweis
Dieser Artikel ist eine Feature-Spezifikation. 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 Feature-Spezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.
Weitere Informationen zum Prozess für die Aufnahme von Funktions-Speclets in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.
Champion Issue: https://github.com/dotnet/csharplang/issues/7700
Zusammenfassung
In C# 12 wurde Unterstützung für das Erstellen von Instanzen auch von anderen Auflistungstypen als Arrays hinzugefügt.
Siehe Auflistungsausdrücke.
Dieser Vorschlag erweitert params
die Unterstützung auf alle derartigenAuflistungstypen.
Motivation
Ein params
Array-Parameter bietet eine bequeme Möglichkeit, eine Methode aufzurufen, die eine beliebig lange Liste von Argumenten benötigt.
Heute muss der Parameter params
ein Arraytyp sein. Für einen Entwickler kann es jedoch von Vorteil sein, wenn er beim Aufruf von APIs, die andere Collection-Typen verwenden, über den gleichen Komfort verfügen kann. Beispiele sind ImmutableArray<T>
, ReadOnlySpan<T>
oder einfache IEnumerable
sein. Dies gilt insbesondere in Fällen, in dem der Compiler eine implizite Arrayzuordnung zur Erstellung der Auflistung vermeiden kann (ImmutableArray<T>
, ReadOnlySpan<T>
usw.).
In Situationen, in denen eine API einen Auflistungstyp verwendet, fügen Entwickler in der Regel eine params
-Überladung hinzu, die ein Array akzeptiert, die Zielauflistung erstellt und die ursprüngliche Überladung mit dieser Auflistung aufruft. Daher müssen API-Consumer eine zusätzliche Arrayzuordnung verwenden.
Ein weiterer Vorteil ist die Möglichkeit, eine params-Bereichsüberladung hinzuzufügen, die Vorrang vor der Array-Version hat, indem der vorhandene Quellcode neu kompiliert wird.
Detailliertes Design
Methodenparameter
Der Abschnitt Methodenparameter wird wie folgt geändert.
formal_parameter_list
: fixed_parameters
- | fixed_parameters ',' parameter_array
+ | fixed_parameters ',' parameter_collection
- | parameter_array
+ | parameter_collection
;
-parameter_array
+parameter_collection
- : attributes? 'params' array_type identifier
+ : attributes? 'params' 'scoped'? type identifier
;
Eine parameter_collection besteht aus einem Satz optionaler Attribute, einem params
-Modifizierer, einem optionalen scoped
Modifizierer, einem Typ und einem Bezeichner. Eine Parameter Collection deklariert einen einzelnen Parameter des angegebenen Typs mit dem angegebenen Namen.
Beim Typ der Parameterauflistung muss es sich um einen der folgenden gültigen Zieltypen für einen Auflistungsausdruck handeln (siehe https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions):
- Ein eindimensionaler -Arraytyp
T[]
, wobei der -ElementtypT
ist. - Ein Bereichstyp:
System.Span<T>
System.ReadOnlySpan<T>
In diesem Fall ist der ElementtypT
- Ein Typ mit einer geeigneten create-Methode, auf die mindestens so zugegriffen werden kann wie auf das deklarierende Element, und einem entsprechenden Elementtyp, der sich aus dieser Determinierung ergibt.
- Ein Struktur oder Klassentyp, der
System.Collections.IEnumerable
implementiert, wobei Folgendes gilt:Der Typ verfügt über einen Konstruktor, der ohne Argumente aufgerufen werden kann, und der Konstruktor ist mindestens so zugänglich wie der deklarierende Member.
Der Typ weist eine Instanz (keine Erweiterungsmethode)
Add
auf, wobei Folgendes gilt:- Die Methode kann mit einem einzelnen Wertargument aufgerufen werden.
- Wenn die Methode generisch ist, können die Typargumente vom Argument abgeleitet werden.
- Die Methode ist mindestens so barrierefrei wie das deklarierende Element.
In diesem Fall ist der Elementtyp der Iterationstyp des Typs .
- 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 ElementtypT
-
Bei einem Methodenaufruf gestattet eine Parameterauflistung entweder die Angabe eines einzelnen Arguments des angegebenen Parametertyps oder die Angabe von null oder mehr Argumenten des Elementtyps der Auflistung. Parameterauflistungen werden in Parameterauflistungen detaillierter beschrieben.
Eine parameter_collection kann nach einem optionalen Parameter auftreten, darf jedoch keinen Standardwert haben. Das Auslassen von Argumenten für eine parameter_collection würde zur Erstellung einer leeren Auflistung führen.
Parametersammlungen
Der Abschnitt Parameterarrays wird umbenannt und wie folgt geändert.
Ein Parameter, der mit einem params
-Modifikator deklariert wird, ist eine Parameterauflistung. Wenn eine formale Parameterliste eine Parameterauflistung enthält, muss es sich um den letzten Parameter in der Liste handeln und muss vom Typ sein, der im Abschnitt Methodenparameter angegeben ist.
Hinweis: Es ist nicht möglich, den
params
Modifikator mit den Modifikatorenin
,out
oderref
zu kombinieren. Hinweisende
Eine Parameter Collection erlaubt es, Argumente auf eine von zwei Arten in einem Methodenaufruf zu spezifizieren:
- Das Argument für eine Parameterauflistung kann ein einzelner Ausdruck sein, der implizit in den Typ der Parameterauflistung konvertierbar ist. In diesem Fall verhält sich die Parameter Collection genau wie ein Wertparameter.
- Alternativ kann der Aufruf null oder mehr Argumente für die Parameter Collection angeben, wobei jedes Argument ein Ausdruck ist, der implizit in den Elementtyp der Parameter Collection konvertierbar ist. In diesem Fall erstellt der Aufruf eine Instanz des Parameterauflistungstyps gemäß den in Auflistungsausdrücke angegebenen Regeln, als ob die Argumente als Ausdruckselemente in einem Auflistungsausdruck in derselben Reihenfolge verwendet würden, und verwendet die neu erstellte Auflistungsinstanz als tatsächliches Argument. Bei der Erstellung der Auflistungsinstanz werden die ursprünglichen nicht konvertierten Argumente verwendet.
Abgesehen von der Möglichkeit, eine variable Anzahl von Argumenten in einem Aufruf zuzulassen, ist eine Parameter Collection genau gleichwertig mit einem Wertparameter desselben Typs.
Bei der Überladungsauflösung kann möglicherweise eine Methode mit einer Parameterauflistung angewendet werden, entweder im normalen Format oder im erweiterten Format. Die erweiterte Form einer Methode ist nur dann verfügbar, wenn die normale Form der Methode nicht anwendbar ist und wenn eine anwendbare Methode mit derselben Signatur wie die erweiterte Form nicht bereits im selben Typ deklariert ist.
Es kann eine Mehrdeutigkeit zwischen dem normalen Format und dem erweiterten Format der Methode mit einem einzelnen Parameterauflistungsargument entstehen, wenn sie als die Parameterauflistung selbst und gleichzeitig als Element der Parameterauflistung verwendet werden kann. Die Mehrdeutigkeit stellt jedoch kein Problem dar, da sie durch Einfügen einer Umwandlung oder die Verwendung eines Auflistungsausdrucks behoben werden kann, wenn notwendig.
Signatures and overloading (Signaturen und Überladen)
Alle Regeln für den params
-Modifizierer in Signaturen und Überladung bleiben erhalten.
Anwendbares Funktionselement
Der Abschnitt Anwendbares Funktionselement wird wie folgt geändert.
Wenn ein Funktionsmitglied, das eine Parameter Collection enthält, in seiner normalen Form nicht anwendbar ist, kann das Funktionsmitglied stattdessen in seiner erweiterten Form anwendbar sein:
- Wenn es sich bei der Parameterauflistung nicht um ein Array handelt, ist eine erweiterte Form nicht für die Sprachversionen C# 12 und darunter anwendbar.
- Das erweiterte Format wird konstruiert, indem die Parameterauflistung in der Funktionselementdeklaration durch null oder mehr Wertparameter des Elementtyps der Parameterauflistung ersetzt wird, sodass die Anzahl der Argumente in der Argumentliste
A
der Gesamtzahl der Parameter entspricht. WennA
weniger Argumente als die Anzahl der festen Parameter in der Funktionsmitglieddeklaration aufweist, kann die erweiterte Form des Funktionsmitglieds nicht erstellt werden und ist somit nicht anwendbar. - Andernfalls ist die erweiterte Form anwendbar, wenn für jedes Argument in
A
eine der folgenden Bedingungen erfüllt ist:- der Modus der Parameterübergabe des Arguments ist identisch mit dem Modus der Parameterübergabe des entsprechenden Parameters, und
- für einen Parameter mit festem Wert oder einen durch die Erweiterung erstellten Wertparameter existiert eine implizite Konversion vom Ausdruck des Arguments zum Typ des entsprechenden Parameters, oder
- für einen
in
-,out
- oderref
-Parameter ist der Typ des Ausdrucks des Arguments identisch mit dem Typ des entsprechenden Parameters.
- der Parameterübergabemodus des Arguments ist Wert und der Parameterübergabemodus des entsprechenden Parameters ist Eingabe und es existiert eine implizite Konversion vom Ausdruck des Arguments zum Typ des entsprechenden Parameters
- der Modus der Parameterübergabe des Arguments ist identisch mit dem Modus der Parameterübergabe des entsprechenden Parameters, und
Besseres Funktionselement
Der Abschnitt Besseres Funktionselement wird wie folgt geändert.
Angesichts einer Argumentliste A
mit einer Reihe von Argumentausdrücken {E₁, E₂, ..., Eᵥ}
und zwei anwendbaren Funktionselementen Mᵥ
und Mₓ
mit Parametertypen {P₁, P₂, ..., Pᵥ}
und {Q₁, Q₂, ..., Qᵥ}
wird Mᵥ
als ein besseres Funktionselement als Mₓ
definiert, wenn
- für jedes Argument ist die implizite Konversion von
Eᵥ
nachQᵥ
nicht besser als die implizite Konversion vonEᵥ
nachPᵥ
, und - für mindestens ein Argument ist die Konversion von
Eᵥ
nachPᵥ
besser als die Konversion vonEᵥ
nachQᵥ
.
Falls die Parametertypsequenzen {P₁, P₂, ..., Pᵥ}
und {Q₁, Q₂, ..., Qᵥ}
gleichwertig sind (d. h., jede Pᵢ
verfügt über eine Identitätskonvertierung in die entsprechende Qᵢ
), werden die folgenden Bindestrichregeln angewendet, um den besseren Funktionsmember zu ermitteln.
- Wenn
Mᵢ
eine nicht-generische Methode ist undMₑ
eine generische Methode, dann istMᵢ
besser alsMₑ
. - Wenn
Mᵢ
im normalen Format anwendbar ist undMₑ
eine Parameterauflistung besitzt und nur im erweiterten Format anwendbar ist, dann istMᵢ
besser alsMₑ
. - Andernfalls, wenn beide Methoden params Collections haben und nur in ihrer erweiterten Form anwendbar sind, und wenn die params-Collection von
Mᵢ
weniger Elemente hat als die params-Collection vonMₑ
, dann istMᵢ
besser alsMₑ
. - Andernfalls, wenn
Mᵥ
spezifischere Parametertypen hat alsMₓ
, dann istMᵥ
besser alsMₓ
. Lassen Sie{R1, R2, ..., Rn}
und{S1, S2, ..., Sn}
die nicht instanziierten und nicht erweiterten Parametertypen vonMᵥ
undMₓ
darstellen. Die Parametertypen vonMᵥ
sind spezifischer als die vonMₓ
, wenn für jeden ParameterRx
nicht weniger spezifisch ist alsSx
und für mindestens einen ParameterRx
spezifischer ist alsSx
:- Ein Typ-Parameter ist weniger spezifisch als ein Nicht-Typ-Parameter.
- Rekursiv ist ein konstruierter Typ spezifischer als ein anderer konstruierter Typ (mit der gleichen Anzahl von Typargumenten), wenn mindestens ein Typargument spezifischer ist und kein Typargument weniger spezifisch ist als das entsprechende Typargument des anderen.
- Ein Array-Typ ist spezifischer als ein anderer Array-Typ (mit der gleichen Anzahl von Dimensionen), wenn der Elementtyp des ersten spezifischer ist als der Elementtyp des zweiten.
- Wenn ein einzelnes Element kein Non-Lifted Operator ist und das andere ein Lifted Operator ist, ist der Non-Lifted Operator besser.
- Wenn kein Funktionsmitglied als besser eingestuft wurde und alle Parameter von
Mᵥ
ein entsprechendes Argument haben, während für mindestens einen optionalen Parameter inMₓ
Standardargumente ersetzt werden müssen, istMᵥ
besser alsMₓ
. - Wenn für mindestens einen Parameter
Mᵥ
die die bessere Wahl für die Parameterübergabe (§12.6.4.4) als der entsprechende Parameter inMₓ
verwendet und keine der Parameter inMₓ
bessere Wahl für die Parameterübergabe alsMᵥ
verwenden, istMᵥ
besser alsMₓ
. - Andernfalls, wenn beide Methoden Paramsauflistungen aufweisen und nur in ihren erweiterten Formen anwendbar sind, ist
Mᵢ
besser alsMₑ
, wenn derselbe Satz von Argumenten den Auflistungselementen für beide Methoden entspricht und eine der folgenden Bedingungen erfüllt ist (dies entspricht https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):- beide params-Auflistungen sind nicht span_type und es gibt eine implizite Konvertierung der params-Auflistung von
Mᵢ
zur params-Auflistung vonMₑ
- params-Auflistung von
Mᵢ
istSystem.ReadOnlySpan<Eᵢ>
, params-Auflistung vonMₑ
istSystem.Span<Eₑ>
und es gibt eine Identitätskonvertierung vonEᵢ
zuEₑ
- params-Auflistung von
Mᵢ
istSystem.ReadOnlySpan<Eᵢ>
oderSystem.Span<Eᵢ>
, params-Auflistung vonMₑ
ist array_or_array_interface__type mit dem ElementtypEₑ
und es gibt eine Identitätskonvertierung vonEᵢ
zuEₑ
- beide params-Auflistungen sind nicht span_type und es gibt eine implizite Konvertierung der params-Auflistung von
- Andernfalls ist kein Funktionsmitglied besser.
Der Grund, warum die neue Entscheidungsregel an das Ende der Liste platziert wird, ist das letzte Unterelement.
- beide params-Auflistungen sind nicht span_type und es gibt eine implizite Konvertierung der params-Auflistung von
Mᵢ
zur params-Auflistung vonMₑ
Es ist auf Arrays anwendbar und daher würde eine frühere Anwendung der Entscheidungsregel zu einer Verhaltensänderung bei vorhandenen Szenarien führen.
Zum Beispiel:
class Program
{
static void Main()
{
Test(1);
}
static void Test(in int x, params C2[] y) {} // There is an implicit conversion from `C2[]` to `C1[]`
static void Test(int x, params C1[] y) {} // Better candidate because of "better parameter-passing choice"
}
class C1 {}
class C2 : C1 {}
Wenn eine der vorherigen Entscheidungsregeln (einschließlich der Regel für „bessere Argumentumwandlungen“) angewendet wird, kann das Überladungsauflösungsergebnis anders als in dem Fall sein, in dem stattdessen ein expliziter Auflistungsausdruck als Argument verwendet wird.
Zum Beispiel:
class Program
{
static void Test1()
{
M1(['1', '2', '3']); // IEnumerable<char> overload is used because `char` is an exact match
M1('1', '2', '3'); // IEnumerable<char> overload is used because `char` is an exact match
}
static void M1(params IEnumerable<char> value) {}
static void M1(params System.ReadOnlySpan<MyChar> value) {}
class MyChar
{
private readonly int _i;
public MyChar(int i) { _i = i; }
public static implicit operator MyChar(int i) => new MyChar(i);
public static implicit operator char(MyChar c) => (char)c._i;
}
static void Test2()
{
M2([1]); // Span overload is used
M2(1); // Array overload is used, not generic
}
static void M2<T>(params System.Span<T> y){}
static void M2(params int[] y){}
static void Test3()
{
M3("3", ["4"]); // Ambiguity, better-ness of argument conversions goes in opposite directions.
M3("3", "4"); // Ambiguity, better-ness of argument conversions goes in opposite directions.
// Since parameter types are different ("object, string" vs. "string, object"), tie-breaking rules do not apply
}
static void M3(object x, params string[] y) {}
static void M3(string x, params Span<object> y) {}
}
Unser primäres Anliegen sind jedoch Szenarien, in denen sich Überladungen nur durch den Parameterauflistungstyp unterscheiden, die Auflistungstypen jedoch denselben Elementtyp aufweisen. Das Verhalten sollte in diesen Fällen mit expliziten Auflistungsausdrücken übereinstimmen.
Die Bedingung „wenn derselbe Satz von Argumenten den Auflistungselementen für beide Methoden entspricht“ ist wichtig für Szenarien wie:
class Program
{
static void Main()
{
Test(x: 1, y: 2); // Ambiguous
}
static void Test(int x, params System.ReadOnlySpan<int> y) {}
static void Test(int y, params System.Span<int> x) {}
}
Es macht keinen Sinn, Collections zu "vergleichen", die aus verschiedenen Elementen erstellt wurden.
Dieser Abschnitt wurde bei LDM überprüft und genehmigt.
Ein Effekt dieser Regeln besteht darin, dass sie bei Aufruf mit einer leeren Argumentliste mehrdeutig sind, wenn params
unterschiedlicher Elementtypen verfügbar gemacht werden.
Zum Beispiel:
class Program
{
static void Main()
{
// Old scenarios
C.M1(); // Ambiguous since params arrays were introduced
C.M1([]); // Ambiguous since params arrays were introduced
// New scenarios
C.M2(); // Ambiguous in C# 13
C.M2([]); // Ambiguous in C# 13
C.M3(); // Ambiguous in C# 13
C.M3([]); // Ambiguous in C# 13
}
public static void M1(params int[] a) {
}
public static void M1(params int?[] a) {
}
public static void M2(params ReadOnlySpan<int> a) {
}
public static void M2(params Span<int?> a) {
}
public static void M3(params ReadOnlySpan<int> a) {
}
public static void M3(params ReadOnlySpan<int?> a) {
}
}
Da wir den Elementtyp über alles andere priorisieren, scheint dies angemessen zu sein; es gibt nichts, was der Sprache mitteilt, ob der Benutzer in diesem Szenario int?
gegenüber int
vorziehen würde.
Dynamische Bindung
Erweiterte Formate von Kandidaten, die andere als Array-Parameterauflistungen verwenden, werden vom aktuellen C#-Laufzeitbinder nicht als gültige Kandidaten anerkannt.
Wenn der primary_expression keinen Kompilierzeittyp hatdynamic
, wird der Methodenaufruf einer begrenzten Kompilierzeitüberprüfung unterzogen, wie in §12.6.5 Kompilierzeitüberprüfung des dynamischen Elementaufrufs beschrieben.
Wenn nur ein einzelner Kandidat den Test besteht, ist der Aufruf des Kandidaten statisch gebunden, wenn alle folgenden Bedingungen erfüllt sind:
- Der Kandidat ist eine lokale Funktion.
- der Kandidat ist entweder nicht generisch oder seine Typargumente sind explizit angegeben;
- Es gibt keine Mehrdeutigkeit zwischen den normalen und erweiterten Formaten des Kandidaten, die zur Kompilierzeit nicht gelöst werden kann.
Andernfalls ist der invocation_expression dynamisch gebunden.
Wenn nur ein einziger Kandidat den obigen Test bestanden hat:
- wenn dieser Kandidat eine lokale Funktion ist, tritt ein Kompilierfehler auf;
- Wenn dieser Kandidat nur im erweiterten Format mit anderen Parameterauflistungen als Arrays verwendbar ist, tritt ein Kompilierzeitfehler auf.
Wir sollten auch die Verletzung der Spezifikation für Rückgängigmachen/Beheben berücksichtigen, die sich auf lokale Funktionen auswirkt, siehe https://github.com/dotnet/roslyn/issues/71399.
LDM hat bestätigt, dass wir diese Spezifikationsverletzung beheben wollen.
Ausdrucksbaumstrukturen
Auflistungsausdrücke werden Ausdrucksstrukturen nicht unterstützt. Entsprechend werden erweiterte Formate von anderen Parameterauflistungen als Arrays in Ausdrucksstrukturen nicht unterstützt. Wir werden die Art und Weise, wie der Compiler Lambdas für Ausdrucksstrukturen bindet, nicht ändern, um zu vermeiden, dass APIs verwendet werden, die erweiterte Formate von anderen Parameterauflistungen als Arrays verwenden.
Reihenfolge der Auswertung mit anderen Auflistungen als Arrays in nicht trivialen Szenarien
Dieser Abschnitt wurde bei LDM überprüft und genehmigt. Trotz der Tatsache, dass Arrays von anderen Collections abweichen, muss die offizielle Sprachspezifikation keine unterschiedlichen Regeln für Arrays festlegen. Die Abweichungen könnten einfach als ein Artefakt der Implementierung behandelt werden. Gleichzeitig beabsichtigen wir nicht, das vorhandene Verhalten im Hinblick auf Arrays zu ändern.
Benannte Argumente
Eine Collection Instanz wird erstellt und gefüllt, nachdem das lexikalisch vorhergehende Argument ausgewertet wurde, aber bevor das lexikalisch folgende Argument ausgewertet wird.
Zum Beispiel:
class Program
{
static void Main()
{
Test(b: GetB(), c: GetC(), a: GetA());
}
static void Test(int a, int b, params MyCollection c) {}
static int GetA() => 0;
static int GetB() => 0;
static int GetC() => 0;
}
Die Reihenfolge der Auswertung ist die folgende:
-
GetB
wird aufgerufen -
MyCollection
wird erstellt und aufgefüllt,GetC
wird dabei aufgerufen -
GetA
wird aufgerufen -
Test
wird aufgerufen
Beachten Sie, dass im Fall von params array das Array unmittelbar vor dem Aufruf der Zielmethode erstellt wird, nachdem alle Argumente in ihrer lexikalischen Reihenfolge ausgewertet wurden.
Verbundzuweisung
Eine Collection Instanz wird erstellt und befüllt, nachdem der lexikalisch vorhergehende Index ausgewertet wurde, aber bevor der lexikalisch folgende Index ausgewertet wird. Die Instanz wird verwendet, um Getter und Setter des Zielindexers aufzurufen.
Zum Beispiel:
class Program
{
static void Test(Program p)
{
p[GetA(), GetC()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
static int GetC() => 0;
}
Die Reihenfolge der Auswertung ist die folgende:
GetA
wird aufgerufen und im Cache gespeichert-
MyCollection
wird erstellt, aufgefüllt und zwischengespeichert,GetC
wird dabei aufgerufen - Der Getter des Indexers wird mit zwischengespeicherten Werten für Indizes aufgerufen.
- Das Ergebnis wird inkrementiert.
- Der Setter des Indexers wird mit zwischengespeicherten Werten für Indizes und dem Ergebnis des Inkrements aufgerufen.
Ein Beispiel für eine leere Sammlung:
class Program
{
static void Test(Program p)
{
p[GetA()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
}
Die Reihenfolge der Auswertung ist die folgende:
GetA
wird aufgerufen und im Cache gespeichert- Ein leeres
MyCollection
wird erstellt und zwischengespeichert - Der Getter des Indexers wird mit zwischengespeicherten Werten für Indizes aufgerufen.
- Das Ergebnis wird inkrementiert.
- Der Setter des Indexers wird mit zwischengespeicherten Werten für Indizes und dem Ergebnis des Inkrements aufgerufen.
Objektinitialisierer
Eine Collection Instanz wird erstellt und befüllt, nachdem der lexikalisch vorhergehende Index ausgewertet wurde, aber bevor der lexikalisch folgende Index ausgewertet wird. Die Instanz wird verwendet, um den Getter des Indexers so häufig wie nötig aufzurufen, wenn zutreffend.
Zum Beispiel:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA(), GetC()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetC() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
Die Reihenfolge der Auswertung ist die folgende:
GetA
wird aufgerufen und im Cache gespeichert-
MyCollection
wird erstellt, aufgefüllt und zwischengespeichert,GetC
wird dabei aufgerufen - Der Getter des Indexers wird mit zwischengespeicherten Werten für Indizes aufgerufen.
GetF1
wird ausgewertet und dem FeldF1
-vonC1
zugewiesen, wie im vorherigen Schritt zurückgegeben- Der Getter des Indexers wird mit zwischengespeicherten Werten für Indizes aufgerufen.
GetF2
wird ausgewertet und dem FeldF2
-vonC1
zugewiesen, wie im vorherigen Schritt zurückgegeben
Beachten Sie, dass im Fall eines Parameterarrays die Elemente ausgewertet und zwischengespeichert werden. Für jeden Aufruf des Indexer-Getters wird jedoch eine neue Instanz eines Arrays (mit denselben Werten) verwendet. Für das obige Beispiel ist die Reihenfolge der Auswertung die folgende:
GetA
wird aufgerufen und im Cache gespeichertGetC
wird aufgerufen und im Cache gespeichert- Der Getter des Indexers wird mit zwischengespeicherten
GetA
und ein neues Array wird mit zwischengespeichertenGetC
ausgefüllt. GetF1
wird ausgewertet und dem FeldF1
-vonC1
zugewiesen, wie im vorherigen Schritt zurückgegeben- Der Getter des Indexers wird mit zwischengespeicherten
GetA
und ein neues Array wird mit zwischengespeichertenGetC
ausgefüllt. GetF2
wird ausgewertet und dem FeldF2
-vonC1
zugewiesen, wie im vorherigen Schritt zurückgegeben
Ein Beispiel für eine leere Sammlung:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
Die Reihenfolge der Auswertung ist die folgende:
GetA
wird aufgerufen und im Cache gespeichert- Ein leeres
MyCollection
wird erstellt und zwischengespeichert - Der Getter des Indexers wird mit zwischengespeicherten Werten für Indizes aufgerufen.
GetF1
wird ausgewertet und dem FeldF1
-vonC1
zugewiesen, wie im vorherigen Schritt zurückgegeben- Der Getter des Indexers wird mit zwischengespeicherten Werten für Indizes aufgerufen.
GetF2
wird ausgewertet und dem FeldF2
-vonC1
zugewiesen, wie im vorherigen Schritt zurückgegeben
Referenzsicherheit
Der Abschnitt Auflistungsausdruck-Referenzsicherheit gilt für die Erstellung von Parameterauflistungen, wenn APIs im erweiterten Format aufgerufen werden.
params-Parameter sind implizit scoped
, wenn ihr Typ eine ref-Struktur ist. UnscopedRefAttribute kann verwendet werden, um das zu überschreiben.
Metadaten
In den Metadaten könnten wir params
-Parameter, die keine Arrays sind, mit System.ParamArrayAttribute
markieren, so wie params
-Arrays heute bereits markiert sind.
Es sieht jedoch so aus, als wäre es viel sicherer, ein anderes Attribut für Nicht-Array params
Parameter zu verwenden.
Beispielsweise kann der aktuelle VB-Compiler sie weder im normalen noch im erweiterten Format mit ParamArrayAttribute
verbrauchen. Daher wird eine Hinzufügung des Modifizierers „params“ VB-Consumer wahrscheinlich unterbrechen, sehr wahrscheinlich auch Consumer in anderen Sprachen oder Tools.
Daher werden params
-Parameter mit einem neuen System.Runtime.CompilerServices.ParamCollectionAttribute
markiert.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public sealed class ParamCollectionAttribute : Attribute
{
public ParamCollectionAttribute() { }
}
}
Dieser Abschnitt wurde bei LDM überprüft und genehmigt.
Offene Fragen
Stapelzuteilungen
Dies ist ein Zitat aus https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: „Stapelzuteilungen für sehr große Auflistungen könnten den Stapel unterbrechen. Sollte der Compiler eine Heuristik für das Platzieren dieser Daten im Heap haben?
Sollte die Sprache nicht spezifiziert sein, um diese Flexibilität zuzulassen?
Wir sollten der Spezifikation für params Span<T>
folgen.“ Wir sollten die Fragen im Kontext dieses Vorschlags beantworten.
[Gelöst] Implizit scoped
Parameter
Wenn params
einen ref struct
-Parameter modifiziert, sollte er einem Vorschlag zufolge als scoped
deklariert betrachtet werden.
Es wird argumentiert, dass die Anzahl der Fälle, in denen der Parameter abgegrenzt sein sollte, nahezu 100 % beträgt, wenn die BCL-Fälle betrachtet werden. In einigen wenigen Fällen, in denen dies erforderlich ist, könnte der Standardwert mit [UnscopedRef]
überschrieben werden.
Es kann jedoch unerwünscht sein, den Standardwert einfach basierend auf dem Vorhandensein des params
-Modifizierers zu ändern. Dies gilt insbesondere, da in Überschreibungs-/Implementierungsszenarien der params
-Modifizierer nicht übereinstimmen muss.
Lösung:
Params-Parameter werden implizit abgegrenzt – https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.
[Gelöst] Die Durchsetzung von scoped
oder params
für Überschreibungen sollte in Betracht gezogen werden.
Wir haben bereits festgestellt, dass params
Parameter standardmäßig scoped
sein sollten. Dies führt jedoch aufgrund der vorhandenen Regeln für die Neuauslegung von params
zu einem seltsamem Verhalten beim Überschreiben:
class Base
{
internal virtual Span<int> M1(scoped Span<int> s1, params Span<int> s2) => throw null!;
}
class Derived : Base
{
internal override Span<int> M1(Span<int> s1, // Error, missing `scoped` on override
Span<int> s2 // Proposal: Error: parameter must include either `params` or `scoped`
) => throw null!;
}
Es gibt einen Unterschied im Überschreibungsverhalten zwischen params
und scoped
: params
wird implizit geerbt, und damit scoped
, während scoped
selbst nicht implizit geerbt wird und auf jeder Ebene wiederholt werden muss.
Vorschlag: Wir sollten durchsetzen, dass Überschreibungen von params
-Parametern explizit params
oder scoped
angeben müssen, wenn die ursprüngliche Definition ein scoped
-Parameter ist. Mit anderen Worten, s2
in Derived
muss params
, scoped
oder beides haben.
Lösung:
Wir müssen die explizite Angabe von scoped
oder params
beim Überschreiben eines params
-Parameters erfordern, wenn anderer Parameter als ein params
-Parameter hierfür erforderlich wäre – https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides.
[Gelöst] Soll das Vorhandensein erforderlicher Elemente die Deklaration des params
-Parameters verhindern?
Betrachten Sie das folgende Beispiel:
using System.Collections;
using System.Collections.Generic;
public class MyCollection1 : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(long l) => throw null;
public required int F; // Collection has required member and constructor doesn't initialize it explicitly
}
class Program
{
static void Main()
{
Test(2, 3); // error CS9035: Required member 'MyCollection1.F' must be set in the object initializer or attribute constructor.
}
// Proposal: An error is reported for the parameter indicating that the constructor that is required
// to be available doesn't initialize required members. In other words, one is able
// to declare such a parameter under the specified conditions.
static void Test(params MyCollection1 a)
{
}
}
Lösung:
Wir werden die required
-Elemente anhand des Konstruktors validieren, der verwendet wird, um die Berechtigung als params
-Parameter an der Deklarationsstelle zu bestimmen – https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.
Alternativen
Es gibt einen alternativen Vorschlag, der params
nur für ReadOnlySpan<T>
gilt.
Außerdem könnte man sagen, da es jetzt Auflistungsausdrücken in der Sprache gibt, keine Notwendigkeit für die Unterstützung von params
gibt. Für jeden Collection-Typ. Um eine API mit Auflistungstyp zu nutzen, müssen Entwickler einfach zwei Zeichen hinzufügen, [
vor der erweiterten Liste der Argumente und ]
danach. Daher könnte die Unterstützung von params
übertrieben sein, insbesondere, da andere Sprachen den Verbrauch von anderen params
-Parametern als Arrays in absehbarer Zeit voraussichtlich nicht unterstützen werden.
Ähnliche Vorschläge
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params
Zugehöre Designbesprechungen
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements
- 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
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-31.md#params-collections-evaluation-orders
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-22.md#effect-of-language-version-on-overload-resolution-in-presence-of-params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-24.md#adjust-dynamic-binding-rules-for-a-situation-of-a-single-applicable-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-01.md#adjust-binding-rules-in-the-presence-of-a-single-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-03.md#params-collections-and-dynamic
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#params-span-breaks
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#params-span-breaks
C# feature specifications