Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
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/6051
Összefoglalás
A C# 10-ben bevezetett lambda-fejlesztésekre (lásd releváns háttér) való építéshez javasoljuk, hogy támogassa az alapértelmezett paraméterértékeket és params tömböket a lambdákban. Ez lehetővé tenné a felhasználók számára a következő lambdas implementálását:
var addWithDefault = (int addTo = 2) => addTo + 1;
addWithDefault(); // 3
addWithDefault(5); // 6
var counter = (params int[] xs) => xs.Length;
counter(); // 0
counter(1, 2, 3); // 3
Hasonlóképpen, ugyanezt a viselkedést fogjuk engedélyezni a metóduscsoportok esetében is:
var addWithDefault = AddWithDefaultMethod;
addWithDefault(); // 3
addWithDefault(5); // 6
var counter = CountMethod;
counter(); // 0
counter(1, 2); // 2
int AddWithDefaultMethod(int addTo = 2) {
return addTo + 1;
}
int CountMethod(params int[] xs) {
return xs.Length;
}
Releváns háttér
Metóduscsoport átalakítási specifikáció §10.8
Motiváció
A .NET-ökoszisztéma alkalmazás-keretrendszerei nagymértékben kihasználják a lambdákat, hogy a felhasználók gyorsan megírhassanak egy végponthoz társított üzleti logikát.
var app = WebApplication.Create(args);
app.MapPost("/todos/{id}", (TodoService todoService, int id, string task) => {
var todo = todoService.Create(id, task);
return Results.Created(todo);
});
A Lambdas jelenleg nem támogatja az alapértelmezett értékek beállítását a paramétereken, ezért ha egy fejlesztő olyan alkalmazást szeretne létrehozni, amely rugalmas volt azokkal a forgatókönyvekkel szemben, amelyekben a felhasználók nem szolgáltattak adatokat, akkor vagy helyi függvényeket használhatnak, vagy beállíthatják az alapértelmezett értékeket a lambda törzsében, szemben a tömörebb javasolt szintaxissal.
var app = WebApplication.Create(args);
app.MapPost("/todos/{id}", (TodoService todoService, int id, string task = "foo") => {
var todo = todoService.Create(id, task);
return Results.Created(todo);
});
A javasolt szintaxisnak az az előnye is, hogy csökkenti a lambdák és a helyi függvények közötti zavaró különbségeket, így könnyebben érthetővé válik a szerkezetek és a lambdas "felnövekszik" a funkciókhoz anélkül, hogy veszélyeztetné a funkciókat, különösen olyan esetekben, amikor a lambdákat olyan API-kban használják, ahol a metóduscsoportok hivatkozásként is megadhatóak.
Ez a params tömb támogatásának fő motivációja is, amelyre a fent említett használati eset forgatókönyve nem terjed ki.
Például:
var app = WebApplication.Create(args);
Result TodoHandler(TodoService todoService, int id, string task = "foo") {
var todo = todoService.Create(id, task);
return Results.Created(todo);
}
app.MapPost("/todos/{id}", TodoHandler);
Előző viselkedés
A C# 12 előtt, amikor egy felhasználó egy opcionális vagy params paraméterrel rendelkező lambdát implementál, a fordító hibát jelez.
var addWithDefault = (int addTo = 2) => addTo + 1; // error CS1065: Default values are not valid in this context.
var counter = (params int[] xs) => xs.Length; // error CS1670: params is not valid in this context
Ha egy felhasználó olyan metóduscsoportot próbál használni, amelyben az alapul szolgáló metódus opcionális vagy params paraméterrel rendelkezik, ez az információ nem kerül továbbításra, ezért a metódus hívása az elvárt argumentumok számának eltérése miatt nem végez típusellenőrzést.
void M1(int i = 1) { }
var m1 = M1; // Infers Action<int>
m1(); // error CS7036: There is no argument given that corresponds to the required parameter 'obj' of 'Action<int>'
void M2(params int[] xs) { }
var m2 = M2; // Infers Action<int[]>
m2(); // error CS7036: There is no argument given that corresponds to the required parameter 'obj' of 'Action<int[]>'
Új viselkedés
A javaslatot követve (a C# 12 része) az alapértelmezett értékek és params a lambda paraméterekre az alábbi viselkedéssel alkalmazhatók:
var addWithDefault = (int addTo = 2) => addTo + 1;
addWithDefault(); // 3
addWithDefault(5); // 6
var counter = (params int[] xs) => xs.Length;
counter(); // 0
counter(1, 2, 3); // 3
Az alapértelmezett értékek és params a metóduscsoport paramétereire az alábbi metóduscsoport definiálásával alkalmazhatók:
int AddWithDefault(int addTo = 2) {
return addTo + 1;
}
var add1 = AddWithDefault;
add1(); // ok, default parameter value will be used
int Counter(params int[] xs) {
return xs.Length;
}
var counter1 = Counter;
counter1(1, 2, 3); // ok, `params` will be used
Kompatibilitást megszakító változás
C# 12 előtt a metóduscsoport következtetett típusa Action vagy Func, ezért a következő kód lefordul:
void WriteInt(int i = 0) {
Console.Write(i);
}
var writeInt = WriteInt; // Inferred as Action<int>
DoAction(writeInt, 3); // Ok, writeInt is an Action<int>
void DoAction(Action<int> a, int p) {
a(p);
}
int Count(params int[] xs) {
return xs.Length;
}
var counter = Count; // Inferred as Func<int[], int>
DoFunction(counter, 3); // Ok, counter is a Func<int[], int>
int DoFunction(Func<int[], int> f, int p) {
return f(new[] { p });
}
A módosítást követően (a C# 12 része) az ilyen jellegű kód fordítása megszűnik a .NET SDK 7.0.200-ás vagy újabb verziójában.
void WriteInt(int i = 0) {
Console.Write(i);
}
var writeInt = WriteInt; // Inferred as anonymous delegate type
DoAction(writeInt, 3); // Error, cannot convert from anonymous delegate type to Action
void DoAction(Action<int> a, int p) {
a(p);
}
int Count(params int[] xs) {
return xs.Length;
}
var counter = Count; // Inferred as anonymous delegate type
DoFunction(counter, 3); // Error, cannot convert from anonymous delegate type to Func
int DoFunction(Func<int[], int> f, int p) {
return f(new[] { p });
}
Figyelembe kell venni ennek a kompatibilitástörő változásnak a hatását. Szerencsére a var használata egy metóduscsoport típusának következtetésére csak a C# 10 óta támogatott, ezért csak az azóta írt kód, amely kifejezetten erre a viselkedésre támaszkodik, megszakadna.
Részletes kialakítás
Nyelvtani és elemzői módosítások
Ehhez a fejlesztéshez a lambdakifejezések nyelvtanának alábbi módosításaira van szükség.
lambda_expression
: modifier* identifier '=>' (block | expression)
- | attribute_list* modifier* type? lambda_parameters '=>' (block | expression)
+ | attribute_list* modifier* type? lambda_parameter_list '=>' (block | expression)
;
+lambda_parameter_list
+ : lambda_parameters (',' parameter_array)?
+ | parameter_array
+ ;
lambda_parameter
: identifier
- | attribute_list* modifier* type? identifier
+ | attribute_list* modifier* type? identifier default_argument?
;
Vegye figyelembe, hogy ez csak lambdák esetében engedélyezi az alapértelmezett paraméterértékeket és params tömböket, a delegate { } szintaxissal deklarált névtelen metódusok esetében nem.
A lambda paraméterekre ugyanazok a szabályok vonatkoznak, mint a metódusparaméterekre (15.6.2).
- Egy
ref,outvagythismódosítóval rendelkező paraméter nem rendelkezhet alapértelmezett_értékkel. - Előfordulhat, hogy egy parameter_array egy opcionális paraméter után fordul elő, de nem rendelkezik alapértelmezett értékkel – a parameter_array argumentumainak kihagyása ehelyett üres tömb létrehozását eredményezné.
A metóduscsoportok esetében nincs szükség a nyelvhelyesség módosítására, mivel ez a javaslat csak a szemantikát változtatná meg.
A névtelen függvénykonverziókhoz (10.7) a következő kiegészítésre van szükség (félkövérben):
A névtelen függvények
Fkompatibilisek a megadott delegált típusúD:
- [...]
- Ha
Fexplicit módon beírt paraméterlistával rendelkezik, aDminden paramétere ugyanazokkal a típusokkal és módosítókkal rendelkezik, mint a megfelelő paraméterFfigyelmen kívül hagyvaparamsmódosítókat és az alapértelmezett értékeket.
Korábbi javaslatok frissítései
A függvénytípusokhoz a következő (vastagon szedett) hozzátétel szükséges a specifikációjához egy korábbi javaslatban szereplő módosítás alapján.
Egy metóduscsoport természetes típussal rendelkezik, ha a metóduscsoport összes jelölt metódusa közös aláírást tartalmaz, beleértve az alapértelmezett értékeket és a
paramsmódosítókat. (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ódusaláírást jelöl: a paramétertípusokat, alapértelmezett értékeket, hivatkozási típusokat,
paramsmódosítókat, valamint a visszatérési típust és a hivatkozási típust. Az azonos szignatúrával rendelkező névtelen függvénykifejezések vagy metóduscsoportok ugyanazzal a function_typerendelkeznek.
A következő kiegészítés (félkövérrel szedett) szükséges az delegálttípusokra és a specifikációra egy korábbi javaslatban:
A névtelen függvény vagy metóduscsoport delegált típusa paramétertípusokkal
P1, ..., Pnés visszatérési típusRa következő:
- ha bármely paraméter vagy visszatérési érték nem érték, vagy bármely paraméter nem kötelező vagy
params, vagy több mint 16 paraméter van, vagy a paramétertípusok vagy a visszatérési értékek nem érvényes típusargumentumok (például(int* p) => { }), akkor a delegált egy szintetizáltinternalnévtelen delegált típus, amely a névtelen függvénynek vagy metóduscsoportnak megfelelő aláírással rendelkezik, paraméternevekarg1, ..., argnvagyarg, ha egyetlen paraméter; [...]
Binder-módosítások
Új delegálttípusok szintetizálása
A ref vagy out paraméterekkel rendelkező meghatalmazottak viselkedéséhez hasonlóan a delegálási típusok szintetizálva vannak az opcionális vagy params paraméterekkel definiált lambdákhoz vagy metóduscsoportokhoz.
Vegye figyelembe, hogy az alábbi példákban a jelölési a', b'stb. a névtelen delegálási típusokat jelöli.
var addWithDefault = (int addTo = 2) => addTo + 1;
// internal delegate int a'(int arg = 2);
var printString = (string toPrint = "defaultString") => Console.WriteLine(toPrint);
// internal delegate void b'(string arg = "defaultString");
var counter = (params int[] xs) => xs.Length;
// internal delegate int c'(params int[] arg);
string PathJoin(string s1, string s2, string sep = "/") { return $"{s1}{sep}{s2}"; }
var joinFunc = PathJoin;
// internal delegate string d'(string arg1, string arg2, string arg3 = " ");
Átalakítás és egyesítés viselkedése
Az opcionális paraméterekkel rendelkező névtelen meghatalmazottak akkor lesznek egységesítve, ha ugyanaz a paraméter (a pozíció alapján) ugyanazzal az alapértelmezett értékkel rendelkezik, a paraméter nevétől függetlenül.
int E(int j = 13) {
return 11;
}
int F(int k = 0) {
return 3;
}
int G(int x = 13) {
return 4;
}
var a = (int i = 13) => 1;
// internal delegate int b'(int arg = 13);
var b = (int i = 0) => 2;
// internal delegate int c'(int arg = 0);
var c = (int i = 13) => 3;
// internal delegate int b'(int arg = 13);
var d = (int c = 13) => 1;
// internal delegate int b'(int arg = 13);
var e = E;
// internal delegate int b'(int arg = 13);
var f = F;
// internal delegate int c'(int arg = 0);
var g = G;
// internal delegate int b'(int arg = 13);
a = b; // Not allowed
a = c; // Allowed
a = d; // Allowed
c = e; // Allowed
e = f; // Not Allowed
b = f; // Allowed
e = g; // Allowed
d = (int c = 10) => 2; // Warning: default parameter value is different between new lambda
// and synthesized delegate b'. We won't do implicit conversion
Az utolsó paraméterként tömböt tartalmazó névtelen meghatalmazottak akkor lesznek egységesítve, ha az utolsó paraméter ugyanazzal a params módosítóval és tömbtípussal rendelkezik, a paraméter nevétől függetlenül.
int C(int[] xs) {
return xs.Length;
}
int D(params int[] xs) {
return xs.Length;
}
var a = (int[] xs) => xs.Length;
// internal delegate int a'(int[] xs);
var b = (params int[] xs) => xs.Length;
// internal delegate int b'(params int[] xs);
var c = C;
// internal delegate int a'(int[] xs);
var d = D;
// internal delegate int b'(params int[] xs);
a = b; // Not allowed
a = c; // Allowed
b = c; // Not allowed
b = d; // Allowed
c = (params int[] xs) => xs.Length; // Warning: different delegate types; no implicit conversion
d = (int[] xs) => xs.Length; // OK. `d` is `delegate int (params int[] arg)`
Hasonlóképpen, természetesen kompatibilis a névvel ellátott delegátumokkal, amelyek már támogatják az opcionális és params paramétereket.
Ha az alapértelmezett értékek vagy params módosítók eltérnek az átalakításban, a forrás nem lesz használatban, ha lambda kifejezésben szerepel, mivel a lambda nem hívható meg más módon.
Ez a felhasználók számára ellentétesnek tűnhet, ezért figyelmeztetés jelenik meg, ha a forrás alapértelmezett értéke vagy params módosító jelen van, és eltér a célértéktől.
Ha a forrás egy metóduscsoport, önmagában hívható meg, ezért a rendszer nem küld figyelmeztetést.
delegate int DelegateNoDefault(int x);
delegate int DelegateWithDefault(int x = 1);
int MethodNoDefault(int x) => x;
int MethodWithDefault(int x = 2) => x;
DelegateNoDefault d1 = MethodWithDefault; // no warning: source is a method group
DelegateWithDefault d2 = MethodWithDefault; // no warning: source is a method group
DelegateWithDefault d3 = MethodNoDefault; // no warning: source is a method group
DelegateNoDefault d4 = (int x = 1) => x; // warning: source present, target missing
DelegateWithDefault d5 = (int x = 2) => x; // warning: source present, target different
DelegateWithDefault d6 = (int x) => x; // no warning: source missing, target present
delegate int DelegateNoParams(int[] xs);
delegate int DelegateWithParams(params int[] xs);
int MethodNoParams(int[] xs) => xs.Length;
int MethodWithParams(params int[] xs) => xs.Length;
DelegateNoParams d7 = MethodWithParams; // no warning: source is a method group
DelegateWithParams d8 = MethodNoParams; // no warning: source is a method group
DelegateNoParams d9 = (params int[] xs) => xs.Length; // warning: source present, target missing
DelegateWithParams d10 = (int[] xs) => xs.Length; // no warning: source missing, target present
IL/futtatási környezet viselkedése
Az alapértelmezett paraméterértékek a metaadatok számára lesznek kibocsátva. Ennek a funkciónak az IL-értéke nagyon hasonló lesz a lambdákhoz ref és out paraméterekkel kibocsátott IL-hez. Létrejön egy System.Delegate vagy hasonlótól öröklő osztály, és a Invoke metódus .param irányelveket fog tartalmazni az alapértelmezett paraméterértékek vagy System.ParamArrayAttribute beállításához – ahogyan az opcionális vagy params paraméterekkel rendelkező standard nevű meghatalmazott esetében is.
Ezek a delegált típusok futásidőben, a szokásos módon vizsgálhatók.
A kódban a felhasználók betekinthetnek a DefaultValue a lambda vagy metóduscsoporthoz társított ParameterInfo-ben a társított MethodInfohasználatával.
var addWithDefault = (int addTo = 2) => addTo + 1;
int AddWithDefaultMethod(int addTo = 2)
{
return addTo + 1;
}
var defaultParm = addWithDefault.Method.GetParameters()[0].DefaultValue; // 2
var add1 = AddWithDefaultMethod;
defaultParm = add1.Method.GetParameters()[0].DefaultValue; // 2
Kérdések megnyitása
Ezek közül egyik sem lett implementálva. Továbbra is nyílt javaslatok maradnak.
Nyitott kérdés: hogyan működik ez a meglévő DefaultParameterValue attribútummal?
Javasolt válasz: Paritás esetén engedélyezze a lambdas DefaultParameterValue attribútumát, és győződjön meg arról, hogy a delegált generáció viselkedése megegyezik a szintaxis által támogatott alapértelmezett paraméterértékekkel.
var a = (int i = 13) => 1;
// same as
var b = ([DefaultParameterValue(13)] int i) => 1;
b = a; // Allowed
Nyitott kérdés: Először is vegye figyelembe, hogy ez kívül esik a jelenlegi javaslat hatókörén, de a jövőben érdemes lehet megvitatni. Támogatni szeretnénk az alapértelmezett értékeket implicit módon beírt lambda paraméterekkel? Azaz.
delegate void M1(int i = 3);
M1 m = (x = 3) => x + x; // Ok
delegate void M2(long i = 2);
M2 m = (x = 3.0) => ...; //Error: cannot convert implicitly from long to double
Ez a következtetés olyan bonyolult konverziós problémákhoz vezet, amelyek további vitát igényelnek.
Itt a teljesítményre vonatkozó szempontokat is elemezni kell. Ma például a (x = kifejezés soha nem lehet a lambda kifejezés kezdete. Ha ezt a szintaxist engedélyeznék lambda alapértelmezettként, akkor az elemzőnek nagyobb előretekintésre lenne szüksége (egészen a => tokenig), hogy megállapíthassa, hogy egy kifejezés lambda-e.
Tervezési értekezletek
-
LDM 2022-10-10: döntés a
paramstámogatásának az alapértelmezett paraméterértékekkel megegyező módon történő hozzáadásáról.
C# feature specifications