Megosztás a következőn keresztül:


Lambda-fejlesztések

Jegyzet

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

Bajnoki probléma: https://github.com/dotnet/csharplang/issues/4934

Összefoglalás

Javasolt módosítások:

  1. Attribútumokkal rendelkező lambdák engedélyezése
  2. Explicit visszatérési típussal rendelkező lambdák használatának engedélyezése
  3. A lambdas és a metóduscsoportok természetes delegálási típusának következtetése

Motiváció

A lambdas attribútumainak támogatása paritásos metódusokat és helyi függvényeket biztosítana.

Az explicit visszatérési típusok támogatása szimmetriát biztosít a lambda paraméterekkel, ahol explicit típusok határozhatók meg. Az explicit visszatérési típusok engedélyezésével szabályozható a fordító teljesítménye a beágyazott lambdákban is, ahol a túlterhelés feloldásának meg kell kötnie a lambda törzsét az aláírás meghatározásához.

A lambdakifejezések és metóduscsoportok természetes típusa több olyan forgatókönyvet tesz lehetővé, ahol a lambdák és metóduscsoportok explicit delegálási típus nélkül is használhatók, beleértve inicializálóként var deklarációkban.

A lambdák és metóduscsoportok explicit delegálási típusainak megkövetelése nehézséget okoz az ügyfelek számára, és a közelmúltban akadályt jelentett az ASP.NET MapActionfejlesztésében.

ASP.NET MapAction javasolt módosítások nélkül (MapAction()System.Delegate argumentumot használ):

[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 a metóduscsoportok természetes típusaival:

[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);

[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);

ASP.NET MapAction a lambda kifejezések attribútumaival és természetes típusaival:

app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);

Attribútumok

Attribútumok hozzáadhatók a lambda kifejezésekhez és a lambda paraméterekhez. A metódusattribútumok és a paraméterattribútumok közötti kétértelműség elkerülése érdekében az attribútumokat tartalmazó lambda kifejezésnek zárójeles paraméterlistát kell használnia. Nincs szükség paramétertípusokra.

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

Több attribútum is megadható, vesszővel elválasztva ugyanabban az attribútumlistában, vagy különálló attribútumlistákként.

var f = [A1, A2][A3] () => { };    // ok
var g = ([A1][A2, A3] int x) => x; // ok

A szintaxissal deklarált névtelen metódusokhoz delegate { }.

f = [A] delegate { return 1; };         // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['

Az elemző előre meg fogja különböztetni a lambda kifejezést tartalmazó, illetve az elem-hozzárendeléssel rendelkező gyűjteményinicializálót.

var y = new C { [A] = x };    // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x

Az elemző a ?[ egy feltételes elem hozzáférésének kezdeteként fogja kezelni.

x = b ? [A];               // ok
y = b ? [A] () => { } : z; // syntax error at '('

A lambda kifejezés vagy a lambda paraméterek attribútumai a metódus metaadataiba kerülnek, amely a lambdára van leképezve.

Az ügyfelek általában ne támaszkodjanak arra, hogy a lambda-kifejezések és a helyi függvények hogyan képeznek le forrásról metaadatra. A lambdas és a helyi függvények kibocsátása változhat a fordítóverziók között.

Az itt javasolt módosítások a Delegate vezérelt forgatókönyvre irányulnak. Lehetőségnek kell lennie a MethodInfo példányhoz társított Delegate vizsgálatára a lambda kifejezés vagy helyi függvény aláírásának meghatározására, beleértve a fordító által kibocsátott explicit attribútumokat és további metaadatokat, mint például az alapértelmezett paramétereket. Ez lehetővé teszi az olyan csapatok számára, mint a ASP.NET, hogy ugyanazokat a viselkedéseket tegyék elérhetővé a lambdákhoz és a helyi funkciókhoz, mint a szokásos módszerek.

Explicit visszatérési típus

A zárójeles paraméterlista előtt explicit visszatérési típus adható meg.

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?

Az elemző előre meg fogja különböztetni a metódushívást T() a lambda kifejezéstől T () => e.

Az explicit visszatérési típusok nem támogatottak a delegate { } szintaxissal deklarált névtelen metódusok esetében.

f = delegate int { return 1; };         // syntax error
f = delegate int (int x) { return x; }; // syntax error

A metódustípus-következtetésnek pontos következtetést kell végeznie egy explicit lambda visszatérési típusból.

static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>

A lambda visszatérési típustól a delegált visszatérési típusig (a paramétertípusokhoz hasonló viselkedésnek megfelelő) varianciakonverziók nem engedélyezettek.

Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x;   // warning

Az elemző lehetővé teszi a lambda kifejezéseket, amelyek ref típusú eredménnyel bírnak, további zárójelek nélkül a kifejezéseken belül.

d = ref int () => x; // d = (ref int () => x)
F(ref int () => x);  // F((ref int () => x))

var nem használható explicit visszatérési típusként a lambda kifejezésekhez.

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

Természetes (függvény) típus

Egy névtelen függvény kifejezés (§12.19) (lambda kifejezés vagy névtelen metódus) természetes típussal rendelkezik, ha a paraméterek típusa explicit, és a visszatérési típus explicit vagy következtethető (lásd §12.6.3.13).

A metóduscsoportok természetes típusúak, ha a metóduscsoport összes jelölt metódusa közös aláírással rendelkezik. (Ha a metóduscsoport bővítménymetódusokat is tartalmazhat, a jelöltek közé tartozik az azt tartalmazó típus és az összes bővítménymetódus-hatókör.)

A névtelen függvénykifejezések vagy metóduscsoportok természetes típusa egy function_type. A function_type egy metódus-aláírást jelöl: a paramétertípusokat és a ref típusokat, valamint a visszatérési típust és a ref típust. Az azonos aláírású névtelen függvénykifejezések vagy metóduscsoportok ugyanazokkal a function_typerendelkeznek.

Function_types csak néhány konkrét környezetben használhatók:

  • implicit és explicit konverziók
  • metódustípus-következtetés (§12.6.3) és a leggyakoribb típus (§12.6.3.15)
  • var inicializálók

A function_type csak fordítási időpontban létezik: function_types nem jelennek meg a forrásban vagy a metaadatokban.

Konverziók

Egy function_typeF esetén implicit function_type konverziók léteznek.

  • Egy function_typeG, ha a F paraméterei és visszatérési típusai variancia-átalakíthatóak a G paramétereire és visszatérési típusára.
  • A System.MulticastDelegate vagy a System.MulticastDelegate alaposztályai vagy felületei
  • System.Linq.Expressions.Expression vagy System.Linq.Expressions.LambdaExpression

A névtelen függvénykifejezések és metóduscsoportok már rendelkeznek konverzióval a kifejezés delegált típusokra és kifejezésfa típusokra (lásd a névtelen függvénykonverziókat a 10.7. §- és a metóduscsoport konverziókat a 10.8). Ezek a konverziók elegendőek ahhoz, hogy erős típusú delegált típusokká és kifejezésfatípusokká alakíthatók. A fenti function_type átalakítások csak típusú átalakításokat adnak hozzá az alaptípusokhoz: System.MulticastDelegate, System.Linq.Expressions.Expressionstb.

A function_typetípustól eltérő típusból function_type konvertálása nem történt. A function_types nincsenek explicit konverziók, mivel function_types nem hivatkozhatók a forrásban.

A System.MulticastDelegate vagy alaptípusra vagy felületre való átalakítás a névtelen függvényt vagy metóduscsoportot egy megfelelő delegálttípus példányaként valósítja meg. A System.Linq.Expressions.Expression<TDelegate> vagy alaptípusra való átalakítás a lambda kifejezést kifejezésfaként valósítja meg, megfelelő delegálási típussal.

Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => "";                // Expression<Func<string>>
object o = "".Clone;                    // Func<object>

Függvény_típus konverziók nem implicit vagy explicit standard konverziók §10.4, és nem veszik figyelembe annak meghatározásakor, hogy a felhasználó által definiált konverziós operátor alkalmazható-e egy névtelen függvényre vagy metóduscsoportra. A felhasználó által definiált konverziók kiértékelése 10.5.3:

Ahhoz, hogy egy konverziós operátor alkalmazható legyen, a forrástípustól az operátor operandus típusára szabványos átalakítást (§10.4) kell végrehajtani, és az operátor eredménytípusától a céltípusig szabványos átalakítást kell végrehajtani.

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'

Figyelmeztetést jelent egy metóduscsoport implicit átalakítása object, mivel az átalakítás érvényes, de talán nem szándékos.

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

Típuskövetkeztetés

A típuskövetkeztetés meglévő szabályai többnyire változatlanok (lásd 12.6.3. §-t). Az alábbiakban azonban néhány változás bizonyos típus következtetési fázisokra vonatkozik.

Első fázis

Az első fázis (§12.6.3.2) lehetővé teszi egy névtelen függvény számára, hogy Ti-höz kötődjön akkor is, ha Ti nem delegált vagy kifejezésfa típusú (például System.Delegate-re korlátozott típusparaméter esetén).

Az egyes metódusargumentumok Ei:

  • Ha Ei névtelen függvény, és Ti delegált típusú vagy kifejezésfa típusú, explicit paramétertípus-következtetésiEiTi, és explicit visszatérési típusú következtetésiEiTi.
  • Ellenkező esetben, ha Ei típusú U és xi egy értékparaméter, akkor Ti.
  • Ellenkező esetben, ha EiU típussal rendelkezik és xi egy ref vagy out paraméter, akkor egy pontos következtetési történik U-rőlTi-re.
  • Ellenkező esetben nincs következtetés az argumentumra vonatkozóan.

Explicit visszatérési típus következtetési

Egy explicit visszatérési típus következtetése kifejezésből ET a következő módon:

  • Ha a E egy névtelen függvény explicit visszatérési típussal Ur, és T delegált típusú vagy kifejezésfa típusú, Vr visszatérési típussal, akkor pontos következtetési (§12.6.3.9) UrVr.

Javítás

A javítás (§12.6.3.12) biztosítja, hogy az egyéb átalakítások előnyben részesüljenek a típusú_funkció átalakításokkal szemben. (A Lambda-kifejezések és a metóduscsoport-kifejezések csak az alacsonyabb határokhoz járulnak hozzá, ezért a function_types kezelése csak az alacsonyabb határokhoz szükséges.)

Az nem rögzített típusú változók Xi kötöttek, rögzített az alábbiak szerint:

  • A jelölttípusokUj halmaza a Xikötött halmaz összes típusának halmazaként kezdődik, ahol a függvénytípusok figyelmen kívül lesznek hagyva az alsó határban, ha vannak olyan típusok, amelyek nem függvénytípusok.
  • Ezután a Xi mindegyik korlátját megvizsgáljuk: A U minden pontos Xi határát minden olyan Uj jelölttípus el lesz távolítva, amely nem azonos a U-gyel. Minden egyes alsó határ U esetén a Xi-beli minden olyan típus Uj eltávolításra kerül a jelöltkészletből, amelyhez nem vezet egy implicit konverzió U-ből. A U minden felső kötött Xi minden olyan Uj, amelyből nem az U implicit konvertálása törlődik a jelöltkészletből.
  • Ha a többi jelölttípus között Uj van egy egyedi V, amelyből implicit átalakítás történik az összes többi jelölttípusra, akkor XiV.
  • Ellenkező esetben a típus kielemezés sikertelen.

Leggyakoribb típus

A legjobb közös típus (§12.6.3.15) típuskövetkezési szempontból van meghatározva, így a fenti típuskövetkezési változások a leggyakoribb típusra is érvényesek.

var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]

var

A függvénytípusokkal rendelkező névtelen függvények és metóduscsoportok inicializálóként használhatók var deklarációkban.

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> 

A függvénytípusok nem használhatók a hozzárendelésekben az elvetésekhez.

d = () => 0; // ok
_ = () => 1; // error

Delegálási típusok

A névtelen függvény vagy metóduscsoport delegált típusa paramétertípusokkal P1, ..., Pn és visszatérési típus R a következő:

  • ha bármely paraméter vagy visszatérési érték nem érték szerint van átvéve, vagy több mint 16 paraméter van, vagy bármelyik paramétertípus vagy visszatérési érték nem érvényes típusargumentum (például (int* p) => { }), akkor a delegált egy szintetizált internal névtelen delegált típus lesz, amelynek aláírása megegyezik a névtelen függvénnyel vagy metóduscsoporttal, és a paraméternevek arg1, ..., argn vagy arg, ha csak egy paraméter van.
  • ha Rvoid, akkor a meghatalmazott típusa System.Action<P1, ..., Pn>;
  • ellenkező esetben a delegálás típusa System.Func<P1, ..., Pn, R>.

A fordító lehetővé teheti, hogy a jövőben több aláírás kötődjön System.Action<> és System.Func<> típusokhoz (ha például ref struct típusargumentumok engedélyezettek).

A metóduscsoport-aláírásban a modopt() vagy modreq() figyelmen kívül van hagyva a megfelelő delegált típusban.

Ha ugyanabban a fordításban két névtelen függvény vagy metóduscsoport azonos paramétertípusokkal és módosítókkal rendelkező szintetizált delegálttípusokat, valamint azonos visszatérési típust és módosítókat igényel, a fordító ugyanazt a szintetizált delegálttípust fogja használni.

Túlterhelés feloldás

A jobb függvénytag (§12.6.4.3) frissítése olyan tagokat részesít előnyben, amelyeknél sem az átalakítások, sem a lambda kifejezésekből vagy metóduscsoportokból származó típusargumentumok nem foglalnak magukban következtetett típusokat.

Jobb függvénytag

... Egy argumentumlistát A, amely tartalmaz egy argumentumkifejezés készletet {E1, E2, ..., En}, és két alkalmazható függvénytagot Mp és Mq a {P1, P2, ..., Pn} és {Q1, Q2, ..., Qn}paramétertípusokkal, Mp határoz meg, mint jobb függvénytagot, mint Mq

  1. az egyes argumentumok esetén a Ex-ről Px-re történő implicit átalakítás nem minősül típusú függvényátalakításnakés
    • Mp nem általános metódus, vagy Mp egy általános metódus, amely {X1, X2, ..., Xp} típusparamétereket használ, és minden típusparaméterhez Xi a típusargumentum kifejezésből vagy egy function_typekívüli típusból származik, és
    • legalább egy argumentum esetében a Ex-ről Qx-re történő implicit átalakítás egy function_type_conversion, vagy ha Mq egy generikus metódus, amely típusparamétereket tartalmaz {Y1, Y2, ..., Yq}, és legalább az egyik típusparaméter esetén Yi a típusargumentum egy function_typealapján van kikövetkeztetve, vagy
  2. minden argumentum esetében az Ex és Qx közötti implicit átalakítás nem jobb, mint a Ex és Pxközötti implicit átalakítás, és legalább egy argumentum esetében a Ex-ről Px-ra való átalakítás jobb, mint a Ex-ról Qx- ra történő átalakítás.

A kifejezésből való jobb átalakítás (12.6.4.5) frissül, hogy az olyan konverziókat részesítse előnyben, amelyek nem tartalmaztak a lambda-kifejezésekből vagy metóduscsoportokból származó következtetési típusokat.

Jobb konverzió a kifejezésből

Adott egy implicit konverzió C1, amely kifejezést E a T1típusra konvertál, és egy másik implicit konverzió C2, amely kifejezést E a T2típusra konvertál, ekkor C1 egy jobb konverzió, mint C2, ha:

  1. C1 nem function_type_conversion, és C2function_type_conversionvagy
  2. E nem állandó interpolated_string_expression, C1 egy implicit_string_handler_conversion, T1 egy applicable_interpolated_string_handler_type, és C2 nem egy implicit_string_handler_conversion, vagy
  3. E nem egyezik meg pontosan T2-gyel, és legalább az alábbiak egyike igaz:

Szintaxis

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?
  ;

Nyitott problémák

Az alapértelmezett értékeket támogatni kell-e a lambda kifejezésparaméterek esetében a teljesség érdekében?

Legyen System.Diagnostics.ConditionalAttribute tiltva a lambdakifejezések esetében, hiszen kevés olyan forgatókönyv létezik, ahol egy lambdakifejezés feltételesen használható?

([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?

Elérhetővé kell-e tenni a function_type a fordító API-ból az eredményként kapott delegálási típuson kívül?

A kikövetkeztetett delegált típus jelenleg a(z) System.Action<> vagy System.Func<>-t használja, ha a paraméter- és visszatérési típusok érvényes típusargumentumok, a(z) és, legfeljebb 16 paraméter van, és ha a várt Action<> vagy Func<> típus hiányzik, hibaüzenet jelenik meg. Ehelyett a fordítónak System.Action<> vagy System.Func<> kell használnia a ritkaságtól függetlenül? És ha a várt típus hiányzik, szintetizáljon egy delegált típust egyébként?