Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Not
Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.
Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta anteckningarna från LDM (Language Design Meeting) .
Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.
Champion-problem: https://github.com/dotnet/csharplang/issues/4934
Sammanfattning
Föreslagna ändringar:
- Tillåt lambdas med attribut
- Tillåt lambdas med explicit returtyp
- Härled en naturlig delegattyp för lambdas och metodgrupper
Motivation
Stöd för attribut på lambdas skulle ge paritet med metoder och lokala funktioner.
Stöd för explicita returtyper skulle ge symmetri med lambda-parametrar där explicita typer kan anges. Att tillåta explicita returtyper skulle också ge kontroll över kompilatorns prestanda i kapslade lambdas där överbelastningsupplösning måste binda lambda-brödtexten för närvarande för att fastställa signaturen.
En naturlig typ för lambda-uttryck och metodgrupper tillåter fler scenarier där lambdas och metodgrupper kan användas utan en explicit delegattyp, inklusive som initialiserare i var-deklarationer.
Att kräva explicita delegattyper för lambdas och metodgrupper har varit en friktionspunkt för kunder och har blivit ett hinder för att gå vidare i ASP.NET med det senaste arbetet med MapAction.
              ASP.NET MapAction utan föreslagna ändringar (MapAction() tar ett 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 med naturliga typer för metodgrupper:
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);
ASP.NET MapAction- med attribut och naturliga typer för lambda-uttryck:
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
Attribut
Attribut kan läggas till i lambda-uttryck och lambda-parametrar. För att undvika tvetydighet mellan metodattribut och parameterattribut måste ett lambda-uttryck med attribut använda en parentesiserad parameterlista. Parametertyper krävs inte.
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
Flera attribut kan anges, antingen kommaavgränsade i samma attributlista eller som separata attributlistor.
var f = [A1, A2][A3] () => { };    // ok
var g = ([A1][A2, A3] int x) => x; // ok
Attributen stöds inte för de anonyma metoder deklarerade med delegate { } syntax.
f = [A] delegate { return 1; };         // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['
Parsern ser framåt för att särskilja en insamlingsinitierare med en elementtilldelning från en samlingsinitierare med ett lambda-uttryck.
var y = new C { [A] = x };    // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x
Parsern behandlar ?[ som början på en villkorlig elementåtkomst.
x = b ? [A];               // ok
y = b ? [A] () => { } : z; // syntax error at '('
Attribut för lambda-uttrycken eller lambda-parametrarna kommer att genereras som metadata på metoden som motsvarar lambda.
I allmänhet bör kunderna inte vara beroende av hur lambda-uttryck och lokala funktioner mappas från källa till metadata. Hur lambdas och lokala funktioner genereras kan och har ändrats mellan kompilatorversioner.
De ändringar som föreslås här är inriktade på det Delegate drivna scenariot.
Det bör vara giltigt att inspektera MethodInfo som är associerad med en Delegate-instans för att fastställa signaturen för lambda-uttrycket eller den lokala funktionen, inklusive explicita attribut och ytterligare metadata som avges av kompilatorn, såsom standardparametrar.
På så sätt kan team som ASP.NET göra samma beteenden tillgängliga för lambdas och lokala funktioner som vanliga metoder.
Explicit returtyp
En explicit returtyp kan anges före den parentesiserade parameterlistan.
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?
Parsern ser framåt för att skilja ett metodanrop T() från ett lambda-uttryck T () => e.
Explicita returtyper stöds inte för anonyma metoder som deklareras med delegate { } syntax.
f = delegate int { return 1; };         // syntax error
f = delegate int (int x) { return x; }; // syntax error
Metodtypinferens bör göra en exakt slutsats från en explicit lambda-returtyp.
static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>
Variationskonverteringar tillåts inte från lambda-returtyp till delegerad returtyp, som matchar liknande beteende för parametertyper.
Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x;   // warning
Parsersn tillåter lambda-uttryck med ref-returtyper inom uttryck utan extra parenteser.
d = ref int () => x; // d = (ref int () => x)
F(ref int () => x);  // F((ref int () => x))
              var kan inte användas som explicit returtyp för lambda-uttryck.
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
Naturlig funktionstyp
En anonym funktion uttryck (§12.19) (ett lambda-uttryck eller en anonym metod) har en naturlig typ om parametrarna är explicita och returtypen antingen är explicit eller kan härledas (se §12.6.3.13).
En -metodgrupp har en naturlig typ om alla kandidatmetoder i metodgruppen har en gemensam signatur. (Om metodgruppen kan innehålla tilläggsmetoder, innehåller kandidaterna den innehållande typen och alla omfång för tilläggsmetoderna.)
Den naturliga typen av ett anonymt funktionsuttryck eller en metodgrupp är en function_type. En function_type representerar en metodsignatur: parametertyperna och referenstyperna samt returtyp och referenstyp. Anonyma funktionsuttryck eller metodgrupper med samma signatur har samma function_type.
Function_types används endast i några få specifika sammanhang:
- implicita och explicita konverteringar
- metodtypsinferens (§12.6.3) och bästa vanliga typ (§12.6.3.15)
- 
              varinitierare
En function_type finns endast vid kompileringstid: function_types visas inte i källan eller metadata.
Omvandlingar
Från en function_typeF finns implicita function_type konverteringar:
- Till en function_typeGom parametrarna och returtyperna förFär varianskombinerade till parametrarna och returtypen förG
- Alternativ System.MulticastDelegateeller basklasser eller gränssnitt förSystem.MulticastDelegate
- Till System.Linq.Expressions.ExpressionellerSystem.Linq.Expressions.LambdaExpression
Anonyma funktionsuttryck och metodgrupper har redan konverteringar från uttryck för att delegera typer och uttrycksträdstyper (se anonyma funktionskonverteringar §10.7 och metodgruppskonverteringar §10.8). Dessa konverteringar är tillräckliga för att omvandla till starkt skrivna delegattyper och uttrycksträd. De function_type konverteringarna ovan lägger till konverteringar från typ endast till bastyperna: System.MulticastDelegate, System.Linq.Expressions.Expressionosv.
Det finns inga konverteringar till en function_type från en annan typ än en function_type. Det finns inga explicita konverteringar för function_types eftersom function_types inte kan refereras i källan.
En konvertering till System.MulticastDelegate eller bastyp eller gränssnitt förverkligar den anonyma funktionen eller metodgruppen som en instans av en lämplig delegattyp.
En konvertering till System.Linq.Expressions.Expression<TDelegate> eller bastyp förverkligar lambda-uttrycket som ett uttrycksträd med en lämplig delegattyp.
Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => "";                // Expression<Func<string>>
object o = "".Clone;                    // Func<object>
Function_type konverteringar är inte implicita eller explicita standardkonverteringar §10.4 och beaktas inte när du fastställer om en användardefinierad konverteringsoperator ska tillämpas på en anonym funktion eller metodgrupp. Från utvärdering av användardefinierade konverteringar §10.5.3:
För att en konverteringsoperatör ska vara tillämplig måste det vara möjligt att utföra en standardkonvertering (§10.4) från källtypen till operatorns operandtyp, och det måste vara möjligt att utföra en standardkonvertering från operatörens resultattyp till måltypen.
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'
En varning rapporteras för en implicit konvertering av en metodgrupp till objecteftersom konverteringen är giltig men kanske oavsiktlig.
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
Typinferens
De befintliga reglerna för typinferens är i huvudsak oförändrade (se §12.6.3). Det finns dock ett par ändringar nedan för specifika faser av typinferens.
Första fasen
I den första fasen (§12.6.3.2) kan en anonym funktion binda till Ti även om Ti inte är en delegat- eller uttrycksträdstyp (kanske en typparameter som är begränsad till System.Delegate till exempel).
För vart och ett av metodargumenten
Ei:
- Om
Eiär en anonym funktion ochTiär en trädtyp för ombud eller uttryckgörs en explicit parametertypsinferens frånEitillTioch en explicit slutsatsdragning av returtyp görs frånEitillTi.- Om
Eihar en typUochxiär en värdeparameter görs annars en med lägre bindning frånUtillTi.- Om
Eiannars har en typUochxiär enref- ellerout-parameter görs en exakt slutsatsdragningfrånUtillTi.- Annars görs ingen slutsatsdragning för det här argumentet.
Explicit slutsatsdragning av returtyp
En explicit returtypsinferens görs från ett uttryck
Etill en typTpå följande sätt:
- Om
Eär en anonym funktion med explicit returtypUrochTär en delegattyp eller uttrycksträdstyp med returtypVrgörs en exakt slutsatsdragning (§12.6.3.9) frånUrtillVr.
Åtgärdande
Fixering (§12.6.3.12) säkerställer att andra konverteringar föredras framför function_type konverteringar. (Lambda-uttryck och metodgrupputtryck bidrar bara till lägre gränser, så hanteringen av function_types behövs endast för lägre gränser.)
En variabel av typen
Ximed en uppsättning gränser är fast på följande sätt:
- Mängden av kandidattyper
Ujbörjar som mängden av alla typer i mängden av gränser förXidär funktionstyper ignoreras i de lägre gränserna om det finns några typer som inte är funktionstyper.- Vi undersöker sedan varje bindning för
Xii tur och ordning: För varje exakt bundenUavXitas alla typerUjsom inte är identiska medUbort från kandidatuppsättningen. För varje lägre bundenUavXialla typerUjsom det finns inte en implicit konvertering frånUtas bort från kandidatuppsättningen. För varje övre gränsUavXi, tas alla typerUjbort från kandidatuppsättningen som det inte finns någon implicit konvertering till frånU.- Om det bland de återstående kandidattyperna
Ujfinns en unik typVdär det finns en implicit konvertering till alla andra kandidattyper, ärXifast tillV.- Annars misslyckas typinferensen.
Bästa vanliga typ
Bästa vanliga typ (§12.6.3.15) definieras i termer av typinferens så att typinferensändringarna ovan även gäller för bästa vanliga typ.
var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]
var
Anonyma funktioner och metodgrupper med funktionstyper kan användas som initierare i var deklarationer.
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> 
Funktionstyper används inte i tilldelningar för att ta bort.
d = () => 0; // ok
_ = () => 1; // error
Delegattyper
Ombudstypen för den anonyma funktionen eller metodgruppen med parametertyper P1, ..., Pn och returtyp R är:
- om någon parameter eller returvärde inte är ett värde, eller om det finns fler än 16 parametrar, eller om någon av parametertyperna eller returen inte är giltiga typargument (till exempel (int* p) => { }), så är delegatet en syntetiseradinternalanonym delegattyp vars signatur matchar den anonyma funktionen eller metodgruppen, och med parameternamnenarg1, ..., argnellerargför enskilda parametrar.
- om Rärvoid, då är ombudstypenSystem.Action<P1, ..., Pn>.
- annars är ombudstypen System.Func<P1, ..., Pn, R>.
Kompilatorn kan i framtiden tillåta att fler signaturer binder sig till typerna System.Action<> och System.Func<> (om ref struct-typer tillåts som typargument, till exempel).
              modopt() eller modreq() i metodgruppens signatur ignoreras i motsvarande delegattyp.
Om två anonyma funktioner eller metodgrupper i samma kompilering kräver syntetiserade ombudstyper med samma parametertyper och modifierare och samma returtyp och modifierare använder kompilatorn samma syntetiserade ombudstyp.
Överbelastningsupplösning
Bättre funktionsmedlem (§12.6.4.3) uppdateras för att föredra medlemmar där varken några av konverteringarna eller typargumenten bygger på härledda typer från lambda-uttryck eller metodgrupper.
Bättre funktionsmedlem
... Med tanke på en argumentlista
Amed en uppsättning argumentuttryck{E1, E2, ..., En}och två tillämpliga funktionsmedlemmarMpochMqmed parametertyper{P1, P2, ..., Pn}och{Q1, Q2, ..., Qn}, definierasMpsom en bättre funktionsmedlem änMqom
- för varje argument är den implicita konverteringen från
ExtillPxinte en funktionskonverteringstypoch
Mpär en icke-generisk metod ellerMpär en allmän metod med typparametrar{X1, X2, ..., Xp}och för varje typparameterXihärleds typargumentet från ett uttryck eller från en annan typ än en function_typeoch- för minst ett argument är den implicita konverteringen från
ExtillQxen function_type_conversion, ellerMqär en allmän metod med typparametrar{Y1, Y2, ..., Yq}och för minst en typparameterYitypargumentet härleds från en function_typeeller- för varje argument är den implicita konverteringen från
ExtillQxinte bättre än den implicita konverteringen frånExtillPx, och för minst ett argument är konverteringen frånExtillPxbättre än konverteringen frånExtillQx.
Bättre konvertering från uttryck (§12.6.4.5) uppdateras för att föredra konverteringar som inte omfattade härledda typer från lambda-uttryck eller metodgrupper.
Bättre omvandling av uttryck
Med en implicit konvertering
C1som konverterar från ett uttryckEtill en typT1och en implicit konverteringC2som konverteras från ett uttryckEtill en typT2ärC1en bättre konvertering änC2om:
C1är inte en function_type_conversion ochC2är en function_type_conversioneller
Eär en icke-konstant interpolated_string_expression, ochC1är en implicit_string_handler_conversion,T1är en applicable_interpolated_string_handler_type, ochC2är inte en implicit_string_handler_conversion, eller
Ematchar inte exaktT2och minst något av följande gäller:
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?
  ;
Öppna problem
Bör standardvärden stödjas för lambda-uttrycksparametrar för fullständighet?
Bör System.Diagnostics.ConditionalAttribute inte tillåtas i lambda-uttryck eftersom det inte finns många scenarier där ett lambda-uttryck kan användas villkorligt?
([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?
Ska function_type vara tillgänglig från kompilator-API:et, utöver den resulterande delegattypen?
För närvarande använder den härledda delegattypen System.Action<> eller System.Func<> när parameter- och returtyper är giltiga typargument och det inte finns fler än 16 parametrar, och om den förväntade Action<> eller Func<> typen saknas rapporteras ett fel. Ska kompilatorn i stället använda System.Action<> eller System.Func<> oavsett aritet? Och om den förväntade typen saknas syntetiserar du annars en ombudstyp?
C# feature specifications