Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
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/4934
Zusammenfassung
Vorgeschlagene Änderungen:
- Zulassen von Lambdas mit Attributen
- Zulassen von Lambdas mit explizitem Rückgabetyp
- Ermitteln eines natürlichen Delegattyps für Lambdas und Methodengruppen
Motivation
Die Unterstützung von Attributen für Lambdas würde Parität mit Methoden und lokalen Funktionen bieten.
Die Unterstützung für explizite Rückgabetypen würde die Symmetrie mit Lambda-Parametern bereitstellen, bei denen explizite Typen angegeben werden können. Durch Zulassen expliziter Rückgabetypen kann auch die Compilerleistung in geschachtelten Lambda-Ausdrücken überwacht werden, bei denen die Überladungsauflösung derzeit den Lambdatext binden muss, um die Signatur zu bestimmen.
Ein natürlicher Typ für Lambda-Ausdrücke und Methodengruppen ermöglicht mehr Szenarien, in denen Lambdas und Methodengruppen ohne expliziten Delegattyp verwendet werden können, einschließlich als Initialisierer in var Deklarationen.
Die Anforderung expliziter Delegattypen für Lambdas und Methodengruppen war ein Reibungspunkt für Kunden und wurde zu einem Hindernis für den Fortschritt in ASP.NET durch die jüngste Arbeit an MapAction.
ASP.NET MapAction ohne vorgeschlagene Änderungen (MapAction() akzeptiert ein System.Delegate Argument):
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction((Func<Todo>)GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction((Func<Todo, Todo>)PostTodo);
ASP.NET MapAction mit natürlichen Typen für Methodengruppen:
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);
ASP.NET MapAction mit Attributen und natürlichen Typen für Lambda-Ausdrücke:
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
Attribute
Attribute können Lambda-Ausdrücken und Lambda-Parametern hinzugefügt werden. Um Mehrdeutigkeit zwischen Methodenattributen und Parameterattributen zu vermeiden, muss ein Lambda-Ausdruck mit Attributen eine klammerte Parameterliste verwenden. Parametertypen sind nicht erforderlich.
f = [A] () => { }; // [A] lambda
f = [return:A] x => x; // syntax error at '=>'
f = [return:A] (x) => x; // [A] lambda
f = [A] static x => x; // syntax error at '=>'
f = ([A] x) => x; // [A] x
f = ([A] ref int x) => x; // [A] x
Es können mehrere Attribute angegeben werden, entweder durch Kommas getrennt innerhalb derselben Attributliste oder als separate Attributlisten.
var f = [A1, A2][A3] () => { }; // ok
var g = ([A1][A2, A3] int x) => x; // ok
Für anonyme Methoden, die mit der delegate { }-Syntax deklariert werden, werden keine Attribute unterstützt.
f = [A] delegate { return 1; }; // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['
Der Parser sieht voraus, um einen Sammlungsinitialisierer mit einer Elementzuweisung von einem Sammlungsinitialisierer mit einem Lambda-Ausdruck zu unterscheiden.
var y = new C { [A] = x }; // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x
Der Parser behandelt ?[ als Start eines bedingten Elementzugriffs.
x = b ? [A]; // ok
y = b ? [A] () => { } : z; // syntax error at '('
Die Attribute für Lambda-Ausdrücke oder Lambda-Parameter werden in die Metadaten der Methode aufgenommen, die der Lambda-Funktion zugeordnet ist.
Im Allgemeinen sollten Kunden nicht darauf vertrauen, wie Lambda-Ausdrücke und lokale Funktionen von der Quelle in Metadaten abgebildet werden. Wie Lambdas und lokale Funktionen ausgegeben werden, kann und hat sich zwischen Compilerversionen geändert.
Die hier vorgeschlagenen Änderungen beziehen sich auf das Delegate-orientierte Szenario.
Die mit einer MethodInfo-Instanz verknüpfte Delegate sowie die expliziten Attribute und zusätzlichen Metadaten wie Standardparameter, die vom Compiler erzeugt werden, sollten überprüft werden, um die Signatur des Lambda-Ausdrucks oder der lokalen Funktion zu bestimmen.
Auf diese Weise können Teams wie ASP.NET dasselbe Verhalten für Lambdas und lokale Funktionen wie normale Methoden zur Verfügung stellen.
Expliziter Rückgabetyp
Ein expliziter Rückgabetyp kann vor der Klammernparameterliste angegeben werden.
f = T () => default; // ok
f = short x => 1; // syntax error at '=>'
f = ref int (ref int x) => ref x; // ok
f = static void (_) => { }; // ok
f = async async (async async) => async; // ok?
Der Parser sieht voraus, um einen Methodenaufruf T() von einem Lambda-Ausdruck T () => ezu unterscheiden.
Explizite Rückgabetypen werden für anonyme Methoden, die mit delegate { } Syntax deklariert wurden, nicht unterstützt.
f = delegate int { return 1; }; // syntax error
f = delegate int (int x) { return x; }; // syntax error
Die Methodentypausleitung sollte eine genaue Ableitung von einem expliziten Lambda-Rückgabetyp vornehmen.
static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>
Es sind keine Varianzkonvertierungen vom Lambda-Rückgabetyp zum Delegatrückgabetyp zulässig (dasselbe gilt auch für Parametertypen).
Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x; // warning
Der Parser ermöglicht Lambda-Ausdrücke mit Rückgabetyp ref innerhalb von Ausdrücken ohne zusätzliche Klammern.
d = ref int () => x; // d = (ref int () => x)
F(ref int () => x); // F((ref int () => x))
var kann nicht als expliziter Rückgabetyp für Lambda-Ausdrücke verwendet werden.
class var { }
d = var (var v) => v; // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = @var (var v) => v; // ok
d = ref var (ref var v) => ref v; // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = ref @var (ref var v) => ref v; // ok
Natürlicher Typ (Funktion)
Eine anonyme Funktion Ausdruck (§12.19) (ein Lambda-Ausdruck oder eine anonyme Methode) hat einen natürlichen Typ, wenn die Parametertypen explizit sind und der Rückgabetyp entweder explizit oder abgeleitet werden kann (siehe §12.6.3.13).
Eine Methodengruppe hat einen natürlichen Typ, wenn alle Kandidatenmethoden in der Methodengruppe eine gemeinsame Signatur aufweisen. (Wenn die Methodengruppe Erweiterungsmethoden enthalten kann, gehören zu den Kandidaten auch der enthaltende Typ und alle Bereiche der Erweiterungsmethoden.)
Der natürliche Typ eines anonymen Funktionsausdrucks oder einer Methodengruppe ist eine function_type. Ein function_type stellt eine Methodensignatur dar: die Parametertypen und Verweisarten sowie der Rückgabetyp und die Verweisart. Anonyme Funktionsausdrücke oder Methodengruppen mit der gleichen Signatur haben den gleichen function_type.
Function_types werden nur in einigen bestimmten Kontexten verwendet:
- implizite und explizite Konvertierungen
- Methodentyp-Ableitung (§12.6.3) und am häufigsten verwendeten Typ (§12.6.3.15)
-
var-Initialisierer
Zur Kompilierungszeit existiert lediglich ein function_type: function_types erscheinen weder im Quellcode noch in den Metadaten.
Umwandlungen
Ausgehend von einem function_typeF können implizite function_type-Konvertierungen durchgeführt werden:
- In einen function_type
G, wenn die Varianz der Parameter und Rückgabetypen vonFin die Parameter und den Rückgabetyp vonGkonvertiert werden können. - In
System.MulticastDelegateoder die Basisklassen oder Schnittstellen vonSystem.MulticastDelegate - In
System.Linq.Expressions.ExpressionoderSystem.Linq.Expressions.LambdaExpression
Anonyme Funktionsausdrücke und Methodengruppen verfügen bereits über Konvertierungen vom Ausdruck zu Delegattypen und Ausdrucksbaumstrukturtypen (siehe anonyme Funktionskonvertierungen §10.7 und Methodengruppenkonvertierungen §10.8). Diese Konvertierungen reichen für die Konvertierung in stark typierte Delegattypen und Ausdrucksbaumstrukturtypen aus. Die oben genannten function_type-Konvertierungen fügen nur Konvertierungen vom Typ zu den Basistypen hinzu: System.MulticastDelegate, System.Linq.Expressions.Expression usw.
Es gibt keine Konvertierungen zu einem Funktionstyp von einem anderen Typ als einem Funktionstyp. Es gibt keine expliziten Konvertierungen für function_types, da auf function_types nicht in der Quelle verwiesen werden kann.
Eine Konvertierung in System.MulticastDelegate oder Basistyp oder Schnittstelle realisiert die anonyme Funktion oder Methodengruppe als Instanz eines geeigneten Delegatentyps.
Eine Konvertierung in denSystem.Linq.Expressions.Expression<TDelegate>- oder Basistyp stellt den Lambda-Ausdruck als Ausdrucksbaumstruktur mit einem geeigneten Delegattyp dar.
Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => ""; // Expression<Func<string>>
object o = "".Clone; // Func<object>
Function_type Konvertierungen sind keine impliziten oder expliziten Standardkonvertierungen §10.4 und werden nicht berücksichtigt, wenn ermittelt wird, ob ein benutzerdefinierter Konvertierungsoperator für eine anonyme Funktion oder Methodengruppe gilt. Aus auswertung der benutzerdefinierten Konvertierungen §10.5.3:
Damit ein Konvertierungsoperator anwendbar ist, muss es möglich sein, eine Standardkonvertierung (§10.4) vom Quelltyp in den Operandentyp des Operators durchzuführen, und es muss möglich sein, eine Standardkonvertierung vom Ergebnistyp des Operators in den Zieltyp durchzuführen.
class C
{
public static implicit operator C(Delegate d) { ... }
}
C c;
c = () => 1; // error: cannot convert lambda expression to type 'C'
c = (C)(() => 2); // error: cannot convert lambda expression to type 'C'
Es wird eine Warnung hinsichtlich einer impliziten Konvertierung einer Methodengruppe in objectgemeldet, da die Konvertierung gültig ist, möglicherweise aber nicht beabsichtigt war.
Random r = new Random();
object obj;
obj = r.NextDouble; // warning: Converting method group to 'object'. Did you intend to invoke the method?
obj = (object)r.NextDouble; // ok
Typinferenz
Die bestehenden Regeln für typische Ableitungen sind größtenteils unverändert (siehe §12.6.3). Weiter unten sind jedoch einige Änderungen im Hinblick auf bestimmte Phasen der Typausleitung aufgeführt.
Erste Phase
In der ersten Phase (§12.6.3.2) kann eine anonyme Funktion an Ti gebunden werden, auch wenn Ti kein Delegat- oder Ausdrucksstrukturtyp ist (z. B. ein Typparameter, der auf System.Delegate beschränkt ist).
Für jedes der Methodenargumente
Ei:
- Wenn
Eieine anonyme Funktion ist undTiein Delegattyp oder Ausdrucksstrukturtypist, wird eine explizite Parametertypableitung vonEizuTierstellt, und eine explizite Rückgabetypableitung wird vonEizuTierstellt.- Wenn aber
Eieinen TypUhat undxiein Wertparameter ist, dann wird ein UntergrenzenrückschlussvonUzuTidurchgeführt.- Andernfalls, wenn
Eieinen TypUhat undxieinref- oderout-Parameter ist, wird eine genaue AbleitungvonUzuTigemacht.- Andernfalls werden für dieses Argument keine Rückschlüsse gezogen.
Explizite Rückgabetyp-Ableitung
Ein expliziter Rückgabetyprückschluss wird von einem Ausdruck
Ein einen TypTfolgendermaßen vorgenommen:
- Wenn
Eeine anonyme Funktion mit explizitem RückgabetypUrist undTein Delegat-Typ oder Ausdrucksbaumtyp mit RückgabetypVrist, wird eine exakte Ableitung (§12.6.3.9) vonUrzuVrgemacht.
Reparatur
Durch Festlegen (§12.6.3.12) wird sichergestellt, dass den function_type-Konvertierungen andere Konvertierungen vorgezogen werden. (Lambda-Ausdrücke und Methodengruppenausdrücke tragen nur zu niedrigeren Grenzen bei, sodass die Behandlung von function_types nur für Untergrenzen erforderlich ist.)
Eine nicht korrigierte Typvariable
Ximit einer Menge von Begrenzungen wird wie folgt korrigiert:
- Die Gruppe der potenziellen Typen
Ujbeginnt als Gruppe aller Typen in der Begrenzungsgruppe fürXi, wobei Funktionstypen an der unteren Grenze ignoriert werden, wenn andere Typen, die keine Funktionstypen sind, vorhanden sind.- Anschließend untersuchen wir wiederum jede Bindung für
Xi: Für jede genaue GrenzeUvonXiwerden alle TypenUj, die nicht mitUidentisch sind, aus der Kandidatengruppe entfernt. Für jede untere GrenzeUvonXiwerden alle TypenUj, in die keine implizite Konvertierung ausUvorhanden ist, aus der Kandidatenmenge entfernt. Für jede obere GrenzeUvonXiwerden alle TypenUj, in die keine implizite Konvertierung inUvorhanden ist, aus der Kandidatengruppe entfernt.- Wenn es unter den verbleibenden Kandidatentypen
Ujeinen eindeutigen TypVgibt, von dem aus eine implizite Konvertierung zu allen anderen Kandidatentypen möglich ist, wirdXiaufVfestgesetzt.- Andernfalls schlägt die Typinferenz fehl.
Am häufigsten verwendete Typen
Der beste gemeinsame Typ (§12.6.3.15) wird in Bezug auf Typableitung definiert, sodass die oben genannten Änderungen bei der Typableitung auch auf den besten gemeinsamen Typ angewendet werden.
var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]
var
Anonyme Funktionen und Methodengruppen mit Funktionstypen können als Initialisierer in var Deklarationen verwendet werden.
var f1 = () => default; // error: cannot infer type
var f2 = x => x; // error: cannot infer type
var f3 = () => 1; // System.Func<int>
var f4 = string () => null; // System.Func<string>
var f5 = delegate (object o) { }; // System.Action<object>
static void F1() { }
static void F1<T>(this T t) { }
static void F2(this string s) { }
var f6 = F1; // error: multiple methods
var f7 = "".F1; // error: the delegate type could not be inferred
var f8 = F2; // System.Action<string>
Funktionstypen werden in Zuordnungen zum Verwerfen nicht verwendet.
d = () => 0; // ok
_ = () => 1; // error
Delegattypen
Der Delegattyp für die anonyme Funktion oder Methodengruppe mit Parametertypen P1, ..., Pn und Rückgabetyp R lautet:
- wenn ein Parameter oder Rückgabewert nicht nach Wert ist oder mehr als 16 Parameter vorhanden sind oder eines der Parametertypen oder Rückgaben ungültige Typargumente sind (z. B.
(int* p) => { }), ist der Delegat ein synthetisierterinternalanonymer Delegattyp mit Signatur, der der anonymen Funktion oder Methodengruppe entspricht, und mit Parameternamenarg1, ..., argnoderarg, wenn ein einzelner Parameter vorhanden ist; - wenn
Rvoidist, ist der DelegattypSystem.Action<P1, ..., Pn>; - andernfalls ist der Delegattyp
System.Func<P1, ..., Pn, R>.
Der Compiler kann zukünftig mehr Signaturen ermöglichen, sich an System.Action<>- und System.Func<>-Typen zu binden (wenn ref struct-Typen z.B. als Typargumente zulässig sind).
modopt() oder modreq() in der Methodengruppensignatur werden im entsprechenden Delegattyp ignoriert.
Wenn zwei anonyme Funktionen oder Methodengruppen in derselben Kompilierung synthetisierte Delegattypen mit denselben Parametertypen und Modifizierern und denselben Rückgabetyp und Modifizierern erfordern, verwendet der Compiler denselben synthetisierten Delegatentyp.
Überladungsauflösung
Das bessere Funktionsmember (§12.6.4.3) wird aktualisiert, damit es Member vorzieht, für die keine der beteiligten Konvertierungen und keines der Typenargumente Typen aus Lambda-Ausdrücken oder Methodengruppen abgeleitet hat.
Besseres Funktionselement
... Angesichts einer Argumentliste
Amit einer Reihe von Argumentausdrücken{E1, E2, ..., En}und zwei anwendbaren FunktionsmembernMpundMqmit Parametertypen{P1, P2, ..., Pn}und{Q1, Q2, ..., Qn}, wirdMpals ein besseres Funktionsmember alsMqdefiniert, wenn
- die implizite Konvertierung von
ExinPxnicht für jedes Argument eine function_type_conversion ist, und
Mpeine nicht generische Methode ist, oderMpeine generische Methode mit Typparametern{X1, X2, ..., Xp}ist und das Typargument für jeden TypparameterXiaus einem Ausdruck oder einem anderen Typ als einem function_type abgeleitet wird, und- die implizite Konvertierung von
ExinQxfür mindestens ein Argument eine function_type_conversion ist, oderMqeine generische Methode mit den Typparametern{Y1, Y2, ..., Yq}ist, und das Typargument für mindestens einen TypparameterYivon einem function_type abgeleitet wird, oder- für jedes Argument ist die implizite Konvertierung von
ExinQxnicht besser als die implizite Konvertierung vonExinPx, und für mindestens ein Argument ist die Konvertierung vonExzuPxbesser als die Konvertierung vonExinQx.
Eine bessere Konvertierung vom Ausdruck (§12.6.4.5) wurde aktualisiert, um Konvertierungen vorzuziehen, die keine von Lambdaausdrücken oder Methodengruppen abgeleiteten Typen beinhalten.
Bessere Konvertierung eines Ausdrucks
Aufgrund einer impliziten Konvertierung
C1, die von einem AusdruckEin einen TypT1konvertiert wird, und einer impliziten KonvertierungC2, die von einem AusdruckEin einen TypT2konvertiert wird, istC1eine bessere Konvertierung alsC2wenn:
C1ist keine function_type_conversion undC2ist eine function_type_conversion oderEist eine nicht konstante interpolated_string_expression,C1ist eine implicit_string_handler_conversion (Handlerkonvertierung einer impliziten Zeichenfolge),T1ist ein applicable_interpolated_string_handler_type (anwendbarer Handler für implizite Zeichenfolgen), undC2ist keine implicit_string_handler_conversion, oderEentspricht nicht genauT2und erfüllt mindestens eine der folgenden Bedingungen:
Syntax
lambda_expression
: modifier* identifier '=>' (block | expression)
| attribute_list* modifier* type? lambda_parameters '=>' (block | expression)
;
lambda_parameters
: lambda_parameter
| '(' (lambda_parameter (',' lambda_parameter)*)? ')'
;
lambda_parameter
: identifier
| attribute_list* modifier* type? identifier equals_value_clause?
;
Offene Probleme
Sollten Standardwerte für Lambda-Ausdrucksparameter zur Vollständigkeit unterstützt werden?
Sollte System.Diagnostics.ConditionalAttribute für Lambda-Ausdrücke verboten werden, da es nur wenige Szenarien gibt, in denen ein Lambda-Ausdruck bedingt verwendet werden könnte?
([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?
Sollte zusätzlich zum resultierenden Delegattyp der function_type über die Compiler-API verfügbar sein?
Derzeit verwendet der abgeleitete Delegattyp System.Action<> oder System.Func<>, wenn Parameter und Rückgabewerte gültige Typargumente und sind, es nicht mehr als 16 Parameter gibt und wenn der erwartete Typ Action<> oder Func<> fehlt, wird ein Fehler gemeldet. Sollte der Compiler stattdessen unabhängig von der Arität System.Action<> oder System.Func<> verwenden? Und wenn der erwartete Typ fehlt, andernfalls einen Delegattyp synthetisieren?
C# feature specifications