Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Nota
Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono registrate nelle note pertinenti del language design meeting (LDM) .
Puoi ottenere maggiori informazioni sul processo di adozione delle speclet di funzionalità nello standard del linguaggio C# nell'articolo sulle specifiche di .
Problema campione: https://github.com/dotnet/csharplang/issues/4934
Sommario
Modifiche proposte:
- Consenti espressioni lambda con attributi
- Consenti espressioni lambda con tipo restituito esplicito
- Dedurre un tipo delegato naturale per lambda e gruppi di metodi
Motivazione
Il supporto per gli attributi nelle espressioni lambda offre parità con i metodi e le funzioni locali.
Il supporto per i tipi restituiti espliciti fornisce la simmetria con parametri lambda in cui è possibile specificare tipi espliciti. Consentire tipi di ritorno espliciti fornisce anche un controllo delle prestazioni del compilatore nelle lambde annidate, in cui la risoluzione dell'overload deve attualmente vincolare il corpo della lambda per determinare la firma.
Un tipo naturale per espressioni lambda e gruppi di metodi consentirà più scenari in cui le espressioni lambda e i gruppi di metodi possono essere usati senza un tipo delegato esplicito, inclusi gli inizializzatori nelle dichiarazioni di var.
La richiesta di tipi delegati espliciti per le espressioni lambda e i gruppi di metodi è stato un punto di attrito per i clienti ed è diventato un ostacolo al progresso in ASP.NET con i recenti lavori su MapAction.
ASP.NET MapAction senza modifiche suggerite (MapAction() accetta un argomento 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 con tipi naturali per i gruppi di metodi:
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);
ASP.NET MapAction con attributi e tipi naturali per le espressioni lambda:
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
Attributi
Gli attributi possono essere aggiunti alle espressioni lambda e ai parametri lambda. Per evitare ambiguità tra attributi del metodo e attributi dei parametri, un'espressione lambda con attributi deve usare un elenco di parametri racchiusi tra parentesi. I tipi di parametro non sono obbligatori.
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
È possibile specificare più attributi, separati da virgole all'interno dello stesso elenco di attributi o come elenchi di attributi separati.
var f = [A1, A2][A3] () => { }; // ok
var g = ([A1][A2, A3] int x) => x; // ok
Gli attributi non sono supportati per metodi anonimi dichiarati con delegate { } sintassi.
f = [A] delegate { return 1; }; // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['
Il parser esaminerà in anticipo per distinguere un inizializzatore di raccolta con un'assegnazione di elemento da un inizializzatore di raccolta con un'espressione lambda.
var y = new C { [A] = x }; // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x
Il parser considererà ?[ come inizio di un accesso condizionale agli elementi.
x = b ? [A]; // ok
y = b ? [A] () => { } : z; // syntax error at '('
Gli attributi dell'espressione lambda o dei parametri lambda verranno emessi nei metadati sul metodo che corrisponde alla lambda.
In generale, i clienti non devono dipendere dal modo in cui le espressioni lambda e le funzioni locali eseguono il mapping dall'origine ai metadati. Il modo in cui le espressioni lambda e le funzioni locali possono essere generate è stato, e può ancora, cambiare tra le versioni del compilatore.
Le modifiche proposte di seguito sono destinate allo scenario basato su Delegate.
Deve essere valido esaminare le MethodInfo associate a un'istanza di Delegate per determinare la firma dell'espressione lambda o della funzione locale, inclusi gli attributi espliciti e i metadati aggiuntivi generati dal compilatore, ad esempio i parametri predefiniti.
In questo modo i team, ad esempio ASP.NET, possono rendere disponibili gli stessi comportamenti per le espressioni lambda e le funzioni locali dei metodi ordinari.
Tipo restituito esplicito
È possibile specificare un tipo restituito esplicito prima dell'elenco di parametri tra parentesi.
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?
Il parser esaminerà in anticipo per distinguere una chiamata di metodo T() da un'espressione lambda T () => e.
I tipi restituiti espliciti non sono supportati per i metodi anonimi dichiarati con sintassi delegate { }.
f = delegate int { return 1; }; // syntax error
f = delegate int (int x) { return x; }; // syntax error
L'inferenza del tipo di metodo deve effettuare un'inferenza esatta a partire da un tipo di ritorno lambda esplicito.
static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>
Le conversioni di varianza non sono consentite dal tipo restituito lambda al tipo restituito delegato (corrispondente a un comportamento simile per i tipi di parametro).
Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x; // warning
Il parser consente espressioni lambda di tipo restituito ref all'interno di espressioni senza parentesi aggiuntive.
d = ref int () => x; // d = (ref int () => x)
F(ref int () => x); // F((ref int () => x))
var non può essere usato come tipo restituito esplicito per le espressioni 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
Tipo naturale (funzione)
Una funzione anonima expression (§12.19) (un'espressione lambda o un metodo anonimo ) ha un tipo naturale se i tipi di parametri sono espliciti e il tipo restituito è esplicito o può essere dedotto (vedere §12.6.3.13).
Un gruppo di metodi ha un tipo naturale se tutti i metodi candidati nel gruppo di metodi hanno una firma comune. Se il gruppo di metodi può includere metodi di estensione, i candidati includono il tipo contenitore e tutti gli ambiti del metodo di estensione.
Il tipo naturale di un'espressione di funzione anonima o di un gruppo di metodi è un function_type. Un function_type rappresenta una firma del metodo: i tipi di parametro e i tipi ref e il tipo restituito e il tipo di riferimento. Le espressioni di funzione anonime o i gruppi di metodi con la stessa firma hanno lo stesso function_type.
Function_types vengono usati solo in alcuni contesti specifici:
- conversioni implicite ed esplicite
- inferenza del tipo di metodo (§12.6.3) e tipo comune migliore (§12.6.3.15)
- inizializzatori
var
Esiste un tipo_di_funzione solo durante la fase di compilazione: i tipi_di_funzione non appaiono nel codice sorgente o nei metadati.
Conversioni
Da un function_typeF sono presenti conversioni implicite di function_type:
- A un function_type
Gse i parametri e i tipi restituiti diFsono convertibili in varianza nei parametri e restituiscono il tipo diG - Per
System.MulticastDelegateo classi o interfacce di base diSystem.MulticastDelegate - Per
System.Linq.Expressions.ExpressionoSystem.Linq.Expressions.LambdaExpression
Le espressioni di funzione anonime e i gruppi di metodi hanno già conversioni da espressioni a tipi delegati e a tipi di albero delle espressioni (vedere conversioni di funzioni anonime §10.7 e conversioni di gruppi di metodi §10.8). Tali conversioni sono sufficienti per la conversione in tipi delegati fortemente tipizzati e tipi di alberi delle espressioni. Le conversioni di function_type precedenti aggiungono conversioni dal tipo solo ai tipi di base: System.MulticastDelegate, System.Linq.Expressions.Expressione così via.
Non sono presenti conversioni in un function_type da un tipo diverso da un function_type. Non esistono conversioni esplicite per function_types perché non è possibile fare riferimento a function_types nell'origine.
Una conversione in System.MulticastDelegate o tipo o interfaccia di base realizza la funzione o il gruppo di metodi anonimo come istanza di un tipo delegato appropriato.
Una conversione in System.Linq.Expressions.Expression<TDelegate> o tipo di base realizza l'espressione lambda come albero delle espressioni con un tipo delegato appropriato.
Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => ""; // Expression<Func<string>>
object o = "".Clone; // Func<object>
Function_type conversioni non sono conversioni standard implicite o esplicite §10.4 e non vengono considerate quando si determina se un operatore di conversione definito dall'utente è applicabile a una funzione o a un gruppo di metodi anonimo. Dalla valutazione delle conversioni definite dall'utente §10.5.3:
Affinché un operatore di conversione sia applicabile, deve essere possibile eseguire una conversione standard (§10.4) dal tipo di origine al tipo di operando dell'operatore e deve essere possibile eseguire una conversione standard dal tipo di risultato dell'operatore al tipo di destinazione.
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'
Viene segnalato un avviso per una conversione implicita di un gruppo di metodi in object, perché la conversione è valida ma forse non intenzionale.
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
Inferenza di tipo
Le regole esistenti per l'inferenza del tipo sono principalmente invariate (vedere §12.6.3). Esistono tuttavia un paio di modifiche di seguito a fasi specifiche di inferenza del tipo.
Prima fase
La prima fase (§12.6.3.2) consente a una funzione anonima di eseguire l'associazione a Ti anche se Ti non è un delegato o un tipo di albero delle espressioni (ad esempio un parametro di tipo vincolato a System.Delegate).
Per ogni argomento del metodo
Ei:
- Se
Eiè una funzione anonima eTiè un tipo delegato o un tipo di albero delle espressioni, viene eseguita un'inferenza esplicita del tipo di parametro daEiaTie viene eseguita un'inferenza esplicita del tipo restituito daEiaTi.- In caso contrario, se
Eiha un tipoUexiè un parametro value, viene un di inferenza con limite inferiore daUaTi.- In caso contrario, se
Eiha un tipoUexiè un parametrorefoout, viene eseguita un'di inferenza esatta daUaTi.- In caso contrario, non viene eseguita alcuna inferenza per questo argomento.
inferenza esplicita del tipo di ritorno
Viene un'inferenza esplicita del tipo restituito da un'espressione
Ea un tipoTnel modo seguente:
- Se
Eè una funzione anonima con tipo restituito esplicitoUreTè un tipo delegato o un tipo di albero delle espressioni con tipo restituitoVrun 'inferenza esatta (§12.6.3.9) viene daUraVr.
Correzione
Correzione (§12.6.3.12) garantisce che le altre conversioni siano preferite rispetto alle conversioni function_type. Le espressioni lambda e le espressioni del gruppo di metodi contribuiscono solo ai limiti inferiori, quindi la gestione di function_types è necessaria solo per i limiti inferiori.
Una variabile di tipo non fissata
Xicon un set di limiti è fissata come indicato di seguito:
- Il set di tipi candidati
Ujinizia come l'insieme di tutti i tipi nei limiti perXi, in cui i tipi di funzione vengono ignorati nei limiti inferiori se sono presenti tipi che non sono tipi di funzione.- Esaminiamo quindi ogni limite per
Xiuno per uno: per ogni limite esattoUdiXi, tutti i tipiUjche non sono identici aUvengono rimossi dall'insieme candidato. Per ogniUcon limite inferiore diXitutti i tipiUja cui non è una conversione implicita daUvengono rimossi dal set di candidati. Per ogniUlimite superiore diXitutti i tipiUjda cui è presente non una conversione implicita inUvengono rimossi dal set candidato.- Se tra i tipi candidati rimanenti
Ujesiste un tipo univocoVda cui è presente una conversione implicita in tutti gli altri tipi candidati,Xiviene risolto inV.- In caso contrario, l'inferenza del tipo ha esito negativo.
Tipo comune migliore
Il tipo comune migliore (§12.6.3.15) è definito in termini di inferenza del tipo in modo che le modifiche all'inferenza del tipo sopra si applichino anche al tipo comune migliore.
var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]
var
Le funzioni anonime e i gruppi di metodi con tipi di funzione possono essere usati come inizializzatori nelle dichiarazioni di 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>
I tipi di funzione non vengono usati nelle assegnazioni agli scarti.
d = () => 0; // ok
_ = () => 1; // error
Tipi delegati
Il tipo delegato per la funzione anonima o il gruppo di metodi con tipi di parametro P1, ..., Pn e tipo restituito R è:
- se un parametro o un valore restituito non è per valore o sono presenti più di 16 parametri o uno qualsiasi dei tipi di parametro o restituiti non sono argomenti di tipo validi (ad esempio,
(int* p) => { }), il delegato è un tipo delegato sintetizzatointernaltipo delegato anonimo con firma corrispondente alla funzione o al gruppo di metodi anonimo e con nomi di parametriarg1, ..., argnoargse un singolo parametro; - se
Rèvoid, il tipo delegato èSystem.Action<P1, ..., Pn>; - in caso contrario, il tipo delegato è
System.Func<P1, ..., Pn, R>.
Il compilatore potrebbe consentire l'associazione di più firme ai tipi System.Action<> e System.Func<> in futuro (se i tipi ref struct sono consentiti come argomenti di tipo, ad esempio).
modopt() o modreq() nella firma del gruppo di metodi vengono ignorati nel tipo delegato corrispondente.
Se due funzioni anonime o gruppi di metodi nella stessa compilazione richiedono tipi delegati sintetizzati con gli stessi tipi di parametro e modificatori e lo stesso tipo restituito e modificatori, il compilatore userà lo stesso tipo delegato sintetizzato.
Risoluzione del sovraccarico
Membro di funzione migliore (§12.6.4.3) viene aggiornato per preferire i membri in cui nessuna delle conversioni e nessuno degli argomenti di tipo coinvolti deduce tipi da espressioni lambda o gruppi di metodi.
Membro di funzione migliore
... Dato un elenco di argomenti
Acon un set di espressioni di argomento{E1, E2, ..., En}e due membri di funzione applicabiliMpeMqcon tipi di parametro{P1, P2, ..., Pn}e{Q1, Q2, ..., Qn},Mpè definito come membro di funzione migliore rispetto aMqse
- per ogni argomento, la conversione implicita da
ExaPxnon è un function_type_conversione
Mpè un metodo non generico oMpè un metodo generico con parametri di tipo{X1, X2, ..., Xp}e per ogni parametro di tipoXil'argomento di tipo viene dedotto da un'espressione o da un tipo diverso da un function_typee- per almeno un argomento, la conversione implicita da
ExaQxè un function_type_conversionoMqè un metodo generico con parametri di tipo{Y1, Y2, ..., Yq}e per almeno un parametro di tipoYil'argomento di tipo viene dedotto da un function_typeo- per ogni argomento, la conversione implicita da
ExaQxnon è migliore della conversione implicita daExaPxe per almeno un argomento, la conversione daExaPxè migliore della conversione daExaQx.
Una migliore conversione dall'espressione (§12.6.4.5) viene aggiornata per preferire le conversioni che non comportavano tipi dedotti da espressioni lambda o gruppi di metodi.
Conversione migliore dall'espressione
Dato un
C1di conversione implicita che esegue la conversione da un'espressioneEa un tipoT1e unC2di conversione implicita che esegue la conversione da un'espressioneEa un tipoT2,C1è una conversione migliore rispetto aC2se:
C1non è un function_type_conversion eC2è un function_type_conversionoEè un interpolated_string_expressionnon costante,C1è un implicit_string_handler_conversion,T1è un applicable_interpolated_string_handler_typeeC2non è un implicit_string_handler_conversionoEnon corrisponde esattamente aT2e almeno uno delle seguenti condizioni è vero:
Sintassi
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?
;
Problemi aperti
I valori predefiniti devono essere supportati per i parametri dell'espressione lambda per la completezza?
È opportuno vietare l'uso di System.Diagnostics.ConditionalAttribute nelle espressioni lambda poiché ci sono pochi scenari in cui un'espressione lambda potrebbe essere usata in modo condizionale?
([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?
Il function_type deve essere disponibile dall'API del compilatore, oltre al tipo delegato risultante?
Attualmente, il tipo delegato dedotto usa System.Action<> o System.Func<> quando i tipi parametro e restituiti sono argomenti di tipo validi e non sono presenti più di 16 parametri e se il tipo di Action<> o Func<> previsto è mancante, viene segnalato un errore. Dovrebbe invece il compilatore usare System.Action<> o System.Func<> indipendentemente dall'arità? E se il tipo previsto è mancante, sintetizzare un tipo delegato in caso contrario?
C# feature specifications