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:
- Povolit lambda s atributy
- Povolit lambdy s explicitním návratovým typem
- 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 typu
G
, pokud jsou parametry a návratové typyF
variance-konvertibilní na parametry a návratový typG
-
System.MulticastDelegate
nebo základní třídy a rozhraníSystem.MulticastDelegate
-
System.Linq.Expressions.Expression
neboSystem.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.Expression
atd.
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 aTi
jetypu delegáta nebo stromu výrazů , explicitní odvození typu parametruEi
doTi
a explicitní návratový typ je vyroben zEi
naTi
.- V opačném případě, pokud
Ei
má typU
axi
je parametr hodnoty, pak dolní mez odvození se vytvoří zU
doTi
.- Pokud má
Ei
typU
axi
je parametrref
neboout
, provede se přesná odvozování zU
naTi
.- 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
E
na typT
následujícím způsobem:
- Pokud je
E
anonymní funkcí s explicitním návratovým typemUr
aT
je typ delegáta nebo stromu výrazu s návratovým typemVr
pak přesnou odvozování (§12.6.3.9) se zUr
doVr
.
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á typu
Xi
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 proXi
, 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 mezU
Xi
všech typůUj
, které nejsou identické sU
, jsou z kandidátské sady odebrány. Pro každou dolní mezU
zXi
jsou všechny typyUj
, ke kterým není možné provést implicitní převod zU
, odebrány z kandidátské sady. Pro každou horní mezU
Xi
všechny typyUj
, ze kterých není implicitní převod naU
jsou z kandidátské sady odebrány.- Pokud mezi zbývajícími kandidátskými typy
Uj
existuje jedinečný typV
, ze kterého existuje implicitní převod na všechny ostatní typy kandidáta,Xi
je pevně nastaven naV
.- 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ý typinternal
anonymního delegáta s podpisem, který odpovídá anonymní funkci nebo skupině metod, a s názvy parametrůarg1, ..., argn
neboarg
, pokud jeden parametr; - pokud je
R
void
, typ delegáta jeSystem.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 funkceMp
aMq
s typy parametrů{P1, P2, ..., Pn}
a{Q1, Q2, ..., Qn}
,Mp
je definován jako lepší člen funkce nežMq
pokud
- pro každý argument není implicitní převod z
Ex
naPx
function_type_conversiona
Mp
je negenerická metoda neboMp
je generická metoda s typovými parametry{X1, X2, ..., Xp}
a pro každý typový parametrXi
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
naQx
funkcí typu , neboMq
je obecná metoda s parametry typu{Y1, Y2, ..., Yq}
a pro alespoň jeden parametr typuYi
je argument typu odvozen z funkce typu , nebo- pro každý argument implicitní převod z
Ex
naQx
není lepší než implicitní převod zEx
naPx
a pro alespoň jeden argument je převod zEx
naPx
lepší než převod zEx
naQx
.
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ýrazuE
na typT1
, a implicitnímu převoduC2
, který převádí z výrazuE
na typT2
,C1
je lepší převod nežC2
, pokud:
C1
není konverze_typu_funkce aC2
je konverze_typu_funkceneboE
není konstantní interpolated_string_expression,C1
je implicit_string_handler_conversion,T1
je applicable_interpolated_string_handler_typeaC2
není implicit_string_handler_conversionneboE
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?
C# feature specifications