Sdílet prostřednictvím


Vylepšení lambda

Poznámka

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách schůzky návrhu jazyka (LDM).

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/4934

Shrnutí

Navrhované změny:

  1. Povolit lambda s atributy
  2. Povolit lambdy s explicitním návratovým typem
  3. Odvoďte přirozený typ delegáta pro lambdy a skupiny metod.

Motivace

Podpora atributů v lambdas by poskytovala paritu s metodami a místními funkcemi.

Podpora explicitních návratových typů by poskytovala symetrii s parametry lambda, kde je možné zadat explicitní typy. Povolení explicitních návratových typů by také poskytovalo kontrolu nad výkonem kompilátoru ve vnořených lambdach, kde rozlišení přetížení musí svázat tělo lambda, aby bylo možné určit podpis.

Přirozený typ výrazů lambda a skupin metod umožní více scénářů, kdy se můžou používat výrazy lambda a skupiny metod bez explicitního typu delegáta, včetně jako inicializátory v deklaracích var.

Vyžadování explicitních typů delegátů pro lambda výrazy a skupiny metod představuje pro zákazníky překážku a stává se překážkou pro pokrok v ASP.NET s nedávnou prací na MapAction.

ASP.NET MapAction bez navrhovaných změn (MapAction() přebírá argument System.Delegate):

[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 s přirozenými typy skupin metod:

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

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

ASP.NET MapAction s atributy a nativními typy pro výrazy lambda:

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

Atributy

Atributy mohou být přidány do výrazů lambda a parametrů lambda. Aby se zabránilo nejednoznačnosti mezi atributy metody a atributy parametrů, výraz lambda s atributy musí používat seznam závorek parametrů. Typy parametrů nejsou povinné.

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

Je možné zadat více atributů, a to buď čárkami oddělenými v rámci stejného seznamu atributů, nebo jako samostatné seznamy atributů.

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

Atributy nejsou podporovány pro anonymní metody, deklarovány pomocí syntaxe delegate { }.

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

Analyzátor se bude dívat dopředu, aby odlišil inicializátor kolekce s přiřazením elementu od inicializátoru kolekce pomocí výrazu lambda.

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

Analyzátor bude zacházet s ?[ jako se zahájením přístupu k podmíněnému prvku.

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

Atributy výrazu lambda nebo parametrů lambda budou uloženy do metadat metody, která se mapuje na lambda.

Zákazníci by obecně neměli spoléhat na to, jak se výrazy lambda a místní funkce mapují ze zdroje na metadata. Způsob, jakým jsou lambda výrazy a místní funkce emitovány, se může a skutečně měnil mezi verzemi kompilátoru.

Navrhované změny jsou zaměřeny na scénář řízený Delegate. Mělo by být platné zkontrolovat MethodInfo přidružené k instanci Delegate a určit podpis výrazu lambda nebo místní funkce, včetně explicitních atributů a dalších metadat vygenerovaných kompilátorem, jako jsou výchozí parametry. To umožňuje týmům, jako je ASP.NET, zpřístupnit stejné chování pro lambda a místní funkce jako běžné metody.

Explicitní návratový typ

Explicitní návratový typ může být zadán před seznamem závorek parametrů.

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?

Analyzátor bude předvídat, aby rozlišil volání metody T() od lambda výrazu T () => e.

Explicitní návratové typy nejsou podporovány pro anonymní metody deklarované se syntaxí delegate { }.

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

Odvození typu metody by mělo provést přesné odvození z explicitního návratového typu lambda.

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

Převody kovariance nejsou povoleny z návratového typu lambda na typ delegáta, odpovídající podobnému chování u typů parametrů.

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

Parser umožňuje vkládání lambda výrazů s návratovými typy ref do výrazů bez dalších závorek.

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

var nelze použít jako explicitní návratový typ výrazů lambda.

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

Přirozený typ (funkce)

Výraz anonymní funkce (§12.19) (výraz lambda nebo anonymní metoda) má přirozený typ, pokud jsou typy parametrů explicitní a návratový typ je explicitní nebo lze odvodit (viz §12.6.3.13).

Skupina metod má přirozený typ, pokud všechny kandidátské metody ve skupině metod mají společný podpis. (Pokud skupina metod může zahrnovat rozšiřující metody, kandidáti zahrnují typ, který je obsahuje, a všechny obory rozšiřujících metod.)

Přirozený typ anonymního výrazu funkce nebo skupiny metod je function_type. function_type představuje signaturu metody: typy parametrů a druhy ref, a návratový typ a druh ref. Anonymní výrazy funkcí nebo skupiny metod, které mají stejný podpis, mají stejný typ funkce .

Function_types se používají jenom v několika konkrétních kontextech:

  • implicitní a explicitní převody
  • odvození typu metody (§12.6.3) a nejlepší společný typ (§12.6.3.15)
  • var inicializátory

function_type existuje pouze v době kompilace: function_types se nezobrazují ve zdroji nebo metadatech.

Převody

Z function_typeF existují implicitní převody function_type:

  • Pro funkci typuG, pokud jsou parametry a návratové typy F variance-konvertibilní na parametry a návratový typ G
  • System.MulticastDelegate nebo základní třídy a rozhraní System.MulticastDelegate
  • System.Linq.Expressions.Expression nebo System.Linq.Expressions.LambdaExpression

Anonymní výrazy funkcí a skupiny metod již mají převody z výrazu na typy delegátů a stromové typy výrazů (viz anonymní převody funkcí §10.7 a převody skupin metod §10.8). Tyto převody jsou dostatečné pro převod na silně typované typy delegátů a typy stromu výrazu. Výše uvedené převody function_type přidávají převody z typu pouze na základní typy: System.MulticastDelegate, System.Linq.Expressions.Expressionatd.

Neexistují žádné převody na function_type z jiného typu než function_type. Neexistují žádné explicitní převody pro function_types, protože function_types nelze odkazovat ve zdroji.

Převod na System.MulticastDelegate nebo základní typ nebo rozhraní zjistí anonymní funkci nebo skupinu metod jako instanci příslušného typu delegáta. Převod na System.Linq.Expressions.Expression<TDelegate> nebo základní typ realizuje lambda výraz jako strom výrazů s odpovídajícím typem delegáta.

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

Funkce_převodu nejsou implicitní ani explicitní standardní převody §10.4 a nejsou zvažovány při určování, zda je operátor převodu definovaný uživatelem použitelný pro anonymní funkci nebo blok metod. Z vyhodnocení uživatelsky definovaných převodů §10.5.3:

Aby byl operátor převodu použitelný, musí být možné provést standardní převod (§10.4) ze zdrojového typu na typ operandu operátoru a musí být možné provést standardní převod z typu výsledku operátoru na cílový typ.

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'

Zobrazí se upozornění pro implicitní převod skupiny metod na object, protože převod je platný, ale možná neúmyslný.

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

Odvození typu

Stávající pravidla pro odvození typu se většinou nemění (viz §12.6.3). Existuje však několik změn níže ke konkrétním fázím odvození typu.

První fáze

První fáze (§12.6.3.2) umožňuje anonymní funkci vytvořit vazbu na Ti, i když Ti není typ delegáta nebo výrazového stromu (například typový parametr omezený na System.Delegate).

Pro každý argument metody Ei:

  • Pokud Ei je anonymní funkce a Ti jetypu delegáta nebo stromu výrazů , explicitní odvození typu parametruEi do Tia explicitní návratový typ je vyroben z Ei na Ti.
  • V opačném případě, pokud Ei má typ U a xi je parametr hodnoty, pak dolní mez odvození se vytvoří zUdoTi.
  • Pokud má Ei typ U a xi je parametr ref nebo out, provede se přesná odvozování zUnaTi.
  • Jinak se pro tento argument nevyvozuje žádná odvození.

odvození explicitního návratu typu

explicitní odvození návratového typu se provádí z výrazu Ena typ T následujícím způsobem:

  • Pokud je E anonymní funkcí s explicitním návratovým typem Ur a T je typ delegáta nebo stromu výrazu s návratovým typem Vr pak přesnou odvozování (§12.6.3.9) se zUrdoVr.

Stanovení

Oprava (§12.6.3.12) zajišťuje, že ostatní převody mají přednost před převody typu funkce . (Výrazy lambda a výrazy skupiny metod přispívají pouze k dolním hranicím, takže zpracování function_types je nutné pouze pro dolní hranice.)

nefixovaná proměnná typuXi se sadou hranic je fixována následujícím způsobem:

  • Sada kandidátských typů Uj začíná jako množina všech typů v hranicích pro Xi, kde se typy funkcí v dolních mezích ignorují, pokud tam jsou jakékoliv typy, které nejsou typu funkce.
  • Pak prozkoumáme každou mez pro Xi: Pro každou přesnou mez UXi všech typů Uj, které nejsou identické s U, jsou z kandidátské sady odebrány. Pro každou dolní mez U z Xi jsou všechny typy Uj, ke kterým není možné provést implicitní převod z U, odebrány z kandidátské sady. Pro každou horní mez UXi všechny typy Uj, ze kterých není implicitní převod na U jsou z kandidátské sady odebrány.
  • Pokud mezi zbývajícími kandidátskými typy Uj existuje jedinečný typ V, ze kterého existuje implicitní převod na všechny ostatní typy kandidáta, Xi je pevně nastaven na V.
  • V opačném případě se odvození typu nezdaří.

Nejlepší běžný typ

Nejlepší společný typ (§12.6.3.15) je definován z hlediska odvození typu, takže změny odvozování typu výše platí i pro nejlepší společný typ.

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

var

Anonymní funkce a skupiny metod s typy funkcí lze použít jako inicializátory v deklaracích var.

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> 

Typy funkcí se v přiřazeních nepoužívají pro zahození.

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

Typy delegátů

Typ delegáta pro anonymní funkci nebo skupinu metod s typy parametrů P1, ..., Pn a návratovým typem R je:

  • pokud některý parametr nebo návratová hodnota není podle hodnoty nebo existuje více než 16 parametrů nebo některý z typů parametrů nebo návratu nejsou platné argumenty typu (například (int* p) => { }), pak delegát je syntetizovaný typ internal anonymního delegáta s podpisem, který odpovídá anonymní funkci nebo skupině metod, a s názvy parametrů arg1, ..., argn nebo arg, pokud jeden parametr;
  • pokud je Rvoid, typ delegáta je System.Action<P1, ..., Pn>;
  • v opačném případě je typ delegáta System.Func<P1, ..., Pn, R>.

Kompilátor může v budoucnu povolit vytvoření vazby více signatur s typy System.Action<> a System.Func<> (například pokud budou povoleny argumenty typu ref struct).

modopt() nebo modreq() v podpisu skupiny metod jsou ignorovány v odpovídajícím typu delegáta.

Pokud dvě anonymní funkce nebo skupiny metod ve stejné kompilaci vyžadují syntetizované typy delegátů se stejnými typy parametrů a modifikátory a stejný návratový typ a modifikátory, kompilátor použije stejný syntetizovaný typ delegáta.

Rozlišení přetížení

Lepší člen funkce (§12.6.4.3) je aktualizován tak, aby upřednostňoval členy, u kterých se žádný z převodů ani žádný z argumentů typu netýká odvozených typů z výrazů lambda nebo skupin metod.

Lepší člen funkce

... Při A seznamu argumentů se sadou výrazů argumentů {E1, E2, ..., En} a dvěma platnými členy funkce Mp a Mq s typy parametrů {P1, P2, ..., Pn} a {Q1, Q2, ..., Qn}, Mp je definován jako lepší člen funkce než Mq pokud

  1. pro každý argument není implicitní převod z Ex na Pxfunction_type_conversiona
    • Mp je negenerická metoda nebo Mp je generická metoda s typovými parametry {X1, X2, ..., Xp} a pro každý typový parametr Xi se argument typu odvozuje z výrazu nebo z jiného typu než je funkce_typa
    • alespoň pro jeden argument je implicitní převod z Ex na Qx funkcí typu , nebo Mq je obecná metoda s parametry typu {Y1, Y2, ..., Yq} a pro alespoň jeden parametr typu Yi je argument typu odvozen z funkce typu , nebo
  2. pro každý argument implicitní převod z Ex na Qx není lepší než implicitní převod z Ex na Pxa pro alespoň jeden argument je převod z Ex na Px lepší než převod z Ex na Qx.

Lepší převod z výrazu (§12.6.4.5) je aktualizován tak, aby upřednostňoval převody, které nezahrnují odvozené typy z výrazů lambda nebo skupin metod.

Lepší převod z výrazu

Vzhledem k implicitnímu převodu C1, který převádí z výrazu E na typ T1, a implicitnímu převodu C2, který převádí z výrazu E na typ T2, C1 je lepší převod než C2, pokud:

  1. C1 není konverze_typu_funkce a C2 je konverze_typu_funkcenebo
  2. E není konstantní interpolated_string_expression, C1 je implicit_string_handler_conversion, T1 je applicable_interpolated_string_handler_typea C2 není implicit_string_handler_conversionnebo
  3. E přesně neodpovídá T2 a platí alespoň jedna z následujících podmínek:

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

Problémy s otevřením

Mají být pro parametry výrazu lambda podporované výchozí hodnoty pro úplnost?

Měli byste System.Diagnostics.ConditionalAttribute u výrazů lambda nepovolit, protože existuje několik scénářů, kdy je možné výraz lambda použít podmíněně?

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

Má být function_type k dispozici kromě výsledného typu delegáta také z rozhraní API kompilátoru?

V současné době se odvozený typ delegáta používá System.Action<> nebo System.Func<>, pokud jsou typy parametrů a návratové typy platné a argumenty a obsahují nejvýše 16 parametrů. Pokud chybí očekávaný typ Action<> nebo Func<>, zobrazí se chyba. Místo toho by kompilátor měl používat System.Action<> nebo System.Func<> bez ohledu na arity? A pokud očekávaný typ chybí, syntetizujete typ delegáta jinak?