Lambda-verbeteringen
Notitie
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Die verschillen worden vastgelegd in de relevante notities van de Language Design Meeting (LDM) .
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Probleem met Kampioen: https://github.com/dotnet/csharplang/issues/4934
Samenvatting
Voorgestelde wijzigingen:
- Lambdas met kenmerken toestaan
- Lambdas met expliciet retourtype toestaan
- Een natuurlijk delegatetype afleiden voor lambdas en methodegroepen
Motivatie
Ondersteuning voor kenmerken op lambdas zou pariteit bieden met methoden en lokale functies.
Ondersteuning voor expliciete retourtypen biedt symmetrie met lambda-parameters waar expliciete typen kunnen worden opgegeven. Het toestaan van expliciete retourtypen zou ook controle bieden over de prestaties van de compiler in geneste lambdas, waarbij overbelastingsresolutie de lambda-body momenteel moet binden om de handtekening te bepalen.
Een natuurlijk type voor lambda-expressies en methodegroepen biedt meer scenario's waarin lambdas- en methodegroepen kunnen worden gebruikt zonder een expliciet gemachtigdentype, waaronder als initialisatiefunctie in var
declaraties.
Het vereisen van expliciete gedelegeerdentypen voor zowel lambdas als methodegroepen vormt voor klanten een knelpunt en is een belemmering geworden voor de voortgang van ASP.NET met recente werkzaamheden aan MapAction.
ASP.NET MapAction- zonder voorgestelde wijzigingen (MapAction()
gebruikt een 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- met natuurlijke typen voor methodegroepen:
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);
ASP.NET MapAction- met kenmerken en natuurlijke typen voor lambda-expressies:
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
Kenmerken
Kenmerken kunnen worden toegevoegd aan lambda-expressies en lambda-parameters. Om dubbelzinnigheid tussen methodekenmerken en parameterkenmerken te voorkomen, moet een lambda-expressie met kenmerken een lijst met haakjes gebruiken. Parametertypen zijn niet vereist.
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
Er kunnen meerdere kenmerken worden opgegeven, gescheiden door komma's in dezelfde kenmerklijst of als afzonderlijke kenmerklijsten.
var f = [A1, A2][A3] () => { }; // ok
var g = ([A1][A2, A3] int x) => x; // ok
Kenmerken worden niet ondersteund voor anonieme methoden gedeclareerd met delegate { }
syntaxis.
f = [A] delegate { return 1; }; // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['
De parser kijkt vooruit om een initialisatiefunctie voor verzamelingen te onderscheiden met een elementtoewijzing van een initialisatiefunctie voor verzamelingen met een lambda-expressie.
var y = new C { [A] = x }; // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x
De parser behandelt ?[
als het begin van toegang tot voorwaardelijke elementen.
x = b ? [A]; // ok
y = b ? [A] () => { } : z; // syntax error at '('
Kenmerken van de lambda-expressie of lambda-parameters worden toegevoegd aan de metagegevens van de methode die overeenkomt met de lambda.
Over het algemeen moeten gebruikers niet afhankelijk zijn van de manier waarop lambda-expressies en lokale functies worden toegewezen van bron naar metagegevens. Hoe lambdas en lokale functies worden verzonden, kunnen en zijn gewijzigd tussen compilerversies.
De hier voorgestelde wijzigingen zijn gericht op het Delegate
georiënteerde scenario.
Het moet geldig zijn om de MethodInfo
te inspecteren die aan een Delegate
-exemplaar is gekoppeld om de signatuur van de lambda-expressie of lokale functie te bepalen, inclusief expliciete attributen en aanvullende metagegevens die door de compiler worden gegenereerd, zoals standaardparameters.
Hierdoor kunnen teams zoals ASP.NET hetzelfde gedrag beschikbaar maken voor lambdas en lokale functies als gewone methoden.
Expliciet retourtype
Een expliciet retourtype kan worden opgegeven vóór de lijst met haakjes geplaatste parameters.
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?
De parser kijkt vooruit om onderscheid te maken tussen een methode-aanroep T()
van een lambda-expressie T () => e
.
Expliciete retourtypen worden niet ondersteund voor anonieme methoden die zijn gedeclareerd met delegate { }
syntaxis.
f = delegate int { return 1; }; // syntax error
f = delegate int (int x) { return x; }; // syntax error
Inferentie van het methodetype moet een exacte inferentie maken uit een expliciet lambda-retourtype.
static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>
Variantieconversies zijn niet toegestaan van het retourtype van een lambda naar het retourtype van een delegate, zoals vergelijkbaar gedrag voor parametertypen.
Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x; // warning
De parser staat toe dat lambda-expressies met ref
retourtypen binnen expressies worden gebruikt zonder extra haakjes.
d = ref int () => x; // d = (ref int () => x)
F(ref int () => x); // F((ref int () => x))
var
kan niet worden gebruikt als een expliciet retourtype voor lambda-expressies.
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
Natuurlijk functietype
Een anonieme functie expressie (§12.19) (een lambda-expressie of een anonieme methode) heeft een natuurlijk type als de parameters expliciet zijn en het retourtype expliciet is of kan worden afgeleid (zie §12.6.3.13).
Een methodegroep een natuurlijk type heeft als alle kandidaatmethoden in de methodegroep een gemeenschappelijke handtekening hebben. (Als de methodegroep uitbreidingsmethoden kan bevatten, bevatten de kandidaten het bijbehorende type en alle uitbreidingsmethodebereiken.)
Het natuurlijke type van een anonieme functie-expressie of methodegroep is een function_type. Een function_type geeft een methodesignatuur weer: de parametertypen en ref-typen, en het retourtype en ref-type. Anonieme functie-expressies of methodegroepen met dezelfde handtekening hebben dezelfde function_type.
Function_types worden alleen in een paar specifieke contexten gebruikt:
- impliciete en expliciete conversies
- methodetype-inferentie (§12.6.3) en beste gemeenschappelijke type (§12.6.3.15)
-
var
initialisatoren
Er bestaat alleen een function_type tijdens het compileren: function_types worden niet weergegeven in de bron of metagegevens.
Conversies
Vanuit een function_typeF
zijn er impliciete function_type conversies:
- Voor een function_type
G
, als de parameters en retourtypen vanF
variantie-converteerbaar zijn naar de parameters en retourtypen vanG
-
System.MulticastDelegate
of basisklassen of interfaces vanSystem.MulticastDelegate
-
System.Linq.Expressions.Expression
ofSystem.Linq.Expressions.LambdaExpression
Anonieme functie-expressies en methodegroepen hebben al conversies van expressies om typen en expressiestructuurtypen te delegeren (zie anonieme functieconversies §10,7 en methodegroepconversies §10,8). Deze conversies zijn voldoende voor conversie naar sterk getypeerde gedelegeerdentypen en expressiestructuurtypen. De bovengenoemde function_type conversies voegen alleen conversies van het type toe aan de basistypen: System.MulticastDelegate
, System.Linq.Expressions.Expression
, enzovoort.
Er zijn geen conversies naar een function_type van een ander type dan een function_type. Er zijn geen expliciete conversies voor function_types omdat er niet naar function_types kan worden verwezen in de bron.
Een conversie naar System.MulticastDelegate
of basistype of -interface realiseert de anonieme functie of methodegroep als een exemplaar van een geschikt gemachtigdetype.
Een conversie naar System.Linq.Expressions.Expression<TDelegate>
of basistype realiseert de lambda-expressie als een expressiestructuur met een geschikt gemachtigdetype.
Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => ""; // Expression<Func<string>>
object o = "".Clone; // Func<object>
Function_type conversies zijn geen impliciete of expliciete standaardconversies §10,4 en worden niet meegenomen bij het bepalen of een door de gebruiker gedefinieerde conversieoperator van toepassing is op een anonieme functie of methodegroep. Van evaluatie van door de gebruiker gedefinieerde conversies §10.5.3:
Om een conversieoperator van toepassing te kunnen zijn, moet het mogelijk zijn om een standaardconversie uit te voeren (§10,4) van het brontype naar het operandtype van de operator, en moet het mogelijk zijn om een standaardconversie uit te voeren van het resultaattype van de operator naar het doeltype.
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'
Er wordt een waarschuwing gerapporteerd voor een impliciete conversie van een methodegroep naar object
, omdat de conversie geldig is, maar mogelijk onbedoeld.
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
Type inferentie
De bestaande regels voor typedeductie zijn meestal ongewijzigd (zie §12.6.3). Er zijn echter enkele wijzigingen hieronder in specifieke fasen van type-inferentie.
Eerste fase
In de eerste fase (§12.6.3.2) kan een anonieme functie worden verbonden met Ti
, zelfs als Ti
geen type gedelegeerde of expressiestructuurtype is (mogelijk een typeparameter beperkt tot System.Delegate
bijvoorbeeld).
Voor elk van de methodeargumenten
Ei
:
- Als
Ei
een anonieme functie is enTi
een gemachtigde type of expressiestructuurtype is, wordt een expliciete parametertypedeductie gemaakt vanEi
totTi
en wordt een expliciete retourtypedeductie gemaakt vanEi
totTi
.- Anders, als
Ei
een typeU
heeft enxi
een waardeparameter is, wordt een ondergrensdeductie gemaakt vanU
totTi
.- Anders, als
Ei
een typeU
heeft enxi
eenref
ofout
parameter is, wordt een exacte redenering gemaakt vanU
naarTi
.- Anders wordt er geen deductie gemaakt voor dit argument.
Expliciete retourtype-inferentie
Een expliciete retourtypedeductie wordt gemaakt van een expressie
E
om een typeT
op de volgende manier te:
- Als
E
een anonieme functie is met expliciet retourtypeUr
enT
een type gedelegeerde of expressiestructuur is met retourtypeVr
, wordt er een exacte deductie (§12.6.3.9) gemaakt vanUr
totVr
.
Repareren
Het oplossen (§12.6.3.12) zorgt ervoor dat andere conversies de voorkeur krijgen boven function_type conversies. (Lambda-expressies en methodegroepexpressies dragen alleen bij aan ondergrenzen, zodat de verwerking van function_types alleen nodig is voor ondergrenzen.)
Een niet-opgeloste typevariabele
Xi
met een reeks beperkingen wordt als volgt vastgesteld:
- De set kandidaattypen
Uj
begint als de verzameling van alle typen binnen de grenzen voorXi
, waarbij functietypen in de lagere grenzen worden genegeerd als er typen zijn die geen functietypen zijn.- Vervolgens onderzoeken we elke grens voor
Xi
op zijn beurt: voor elke exacte grensU
vanXi
worden alle typenUj
die niet identiek zijn aanU
uit de kandidaatset verwijderd. Voor elke ondergrensU
vanXi
worden alle typenUj
waarvoor er geen impliciete conversie vanU
is uit de kandidaatset verwijderd. Voor elke bovengrensU
vanXi
alle typenUj
waarvan er geen impliciete conversie naarU
worden verwijderd uit de kandidaatset.- Als er onder de overige kandidaattypen
Uj
een uniek typeV
is dat een impliciete conversie naar alle andere kandidaattypen heeft, wordtXi
vastgezet opV
.- Anders mislukt de type-afleiding.
Meest gangbaar type
Het beste gangbare type (§12.6.3.15) wordt gedefinieerd in termen van type-afleiding, zodat de hierboven genoemde wijzigingen in type-afleiding ook van toepassing zijn op het beste gangbare type.
var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]
var
Anonieme functies en methodegroepen met functietypen kunnen worden gebruikt als initialisatiefuncties in var
declaraties.
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>
Functietypen worden niet gebruikt in toekenningen aan ongebruikte waarden.
d = () => 0; // ok
_ = () => 1; // error
Typen gemachtigden
Het gemachtigde type voor de anonieme functie of methodegroep met parametertypen P1, ..., Pn
en retourtype R
is:
- als een parameter of retourwaarde niet op waarde is, of als er meer dan 16 parameters zijn, of een van de parametertypen of return ongeldige typeargumenten zijn (bijvoorbeeld
(int* p) => { }
), is de gemachtigde een gesynthetiseerdinternal
anoniem gemachtigdetype met handtekening die overeenkomt met de anonieme functie of methodegroep, en met parameternamenarg1, ..., argn
ofarg
als één parameter; - als
R
gelijk is aanvoid
, dan is het delegatietypeSystem.Action<P1, ..., Pn>
; - anders is het delegatetype
System.Func<P1, ..., Pn, R>
.
De compiler kan in de toekomst meer signaturen toestaan om te binden aan System.Action<>
en System.Func<>
typen (als ref struct
typen bijvoorbeeld type-argumenten zijn toegestaan).
modopt()
of modreq()
in de signatuur van de methodegroep worden genegeerd in het bijbehorende delegate-type.
Als voor twee anonieme functies of methodegroepen in dezelfde compilatie gesynthetiseerde gedelegeerdentypen met dezelfde parametertypen en modifiers en hetzelfde retourtype en dezelfde wijzigingstype zijn vereist, gebruikt de compiler hetzelfde gesynthetiseerde gemachtigdetype.
Overbelastingsresolutie
Beter functielid (§12.6.4.3) wordt bijgewerkt om de voorkeur te geven aan leden waarbij geen van de conversies en geen van de typeargumenten betrokken afgeleide typen van lambda-expressies of methodegroepen zijn.
Beter functielid
... Gezien een lijst met argumenten
A
met een set argumentexpressies{E1, E2, ..., En}
en twee toepasselijke functieledenMp
enMq
met parametertypen{P1, P2, ..., Pn}
en{Q1, Q2, ..., Qn}
, wordtMp
gedefinieerd als een beter functielid danMq
als
- voor elk argument is de impliciete conversie van
Ex
naarPx
geen functie-typeconversieen
Mp
een niet-algemene methode is ofMp
een algemene methode is met typeparameters{X1, X2, ..., Xp}
en voor elke typeparameterXi
wordt het typeargument afgeleid van een expressie of een ander type dan een function_typeen- Voor ten minste één argument is de impliciete conversie van
Ex
naarQx
een functie_type_conversie, ofMq
is een generieke methode met typeparameters{Y1, Y2, ..., Yq}
, en voor ten minste één typeparameterYi
wordt het typeargument afgeleid van een functie_type, of.- voor elk argument is de impliciete conversie van
Ex
naarQx
niet beter dan de impliciete conversie vanEx
naarPx
, en voor ten minste één argument is de conversie vanEx
naarPx
beter dan de conversie vanEx
naarQx
.
Een betere conversie van expressie (§12.6.4.5) wordt bijgewerkt om de voorkeur te geven aan conversies waarbij geen afgeleide typen van lambda-expressies of methodegroepen zijn betrokken.
Betere conversie van uitdrukking
Gezien een impliciete conversie
C1
die wordt geconverteerd van een expressieE
naar een typeT1
en een impliciete conversieC2
die wordt geconverteerd van een expressieE
naar een typeT2
, isC1
een betere conversie danC2
als:
C1
is geen function_type_conversion enC2
is een function_type_conversionofE
is een niet-constante interpolated_string_expression,C1
is een implicit_string_handler_conversion,T1
is een applicable_interpolated_string_handler_type, enC2
is geen implicit_string_handler_conversion, ofE
komt niet exact overeen metT2
en ten minste één van de volgende geldt:
Syntaxis
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?
;
Openstaande problemen
Moeten standaardwaarden worden ondersteund voor lambda-expressieparameters voor volledigheid?
Moet System.Diagnostics.ConditionalAttribute
niet zijn toegestaan voor lambda-expressies, omdat er weinig scenario's zijn waarin een lambda-expressie voorwaardelijk kan worden gebruikt?
([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?
Moet de function_type beschikbaar zijn vanuit de compiler-API, naast het resulterende gemachtigdentype?
Op dit moment gebruikt het type uitgestelde gemachtigde System.Action<>
of System.Func<>
wanneer parameter- en retourtypen geldige typeargumenten zijn en er niet meer dan 16 parameters zijn en als het verwachte Action<>
of Func<>
type ontbreekt, wordt er een fout gerapporteerd. Moet de compiler in plaats daarvan ongeacht de ariteit System.Action<>
of System.Func<>
gebruiken? En als het verwachte type ontbreekt,synthetiseert u anders een gemachtigdentype?
C# feature specifications