Prima di tutto, lasciatemi dire che la risposta di Jon è corretta. Questa è una delle parti più pelose della specifica, così bravo con Jon per essersi tuffato per primo.
In secondo luogo, lasciatemi dire che questa riga:
Esiste una conversione implicita da un gruppo di metodi a un tipo di delegato compatibile
(enfasi aggiunta) è profondamente fuorviante e sfortunato. Parlerò con Mads sulla rimozione della parola "compatibile" qui.
Il motivo per cui questo è fuorviante e sfortunato è perché sembra che questo richiami alla sezione 15.2, "Delegare la compatibilità". La sezione 15.2 ha descritto la relazione di compatibilità tra metodi e tipi di delegato , ma questa è una questione di convertibilità di gruppi di metodi e tipi di delegati , che è diversa.
Ora che l'abbiamo tolto di mezzo, possiamo esaminare la sezione 6.6 delle specifiche e vedere cosa otteniamo.
Per eseguire la risoluzione del sovraccarico, è necessario prima determinare quali sovraccarichi sono candidati applicabili . Un candidato è applicabile se tutti gli argomenti sono convertibili in modo implicito nei tipi di parametri formali. Considera questa versione semplificata del tuo programma:
class Program
{
delegate void D1();
delegate string D2();
static string X() { return null; }
static void Y(D1 d1) {}
static void Y(D2 d2) {}
static void Main()
{
Y(X);
}
}
Quindi esaminiamolo riga per riga.
Esiste una conversione implicita da un gruppo di metodi a un tipo di delegato compatibile.
Ho già discusso di come la parola "compatibile" sia sfortunata qui. Andare avanti. Ci stiamo chiedendo quando si esegue la risoluzione del sovraccarico su Y (X), il gruppo di metodi X viene convertito in D1? Si converte in D2?
Dato un tipo di delegato D e un'espressione E classificata come gruppo di metodi, esiste una conversione implicita da E a D se E contiene almeno un metodo applicabile [...] a un elenco di argomenti costruito utilizzando il parametro tipi e modificatori di D, come descritto di seguito.
Fin qui tutto bene. X potrebbe contenere un metodo applicabile con gli elenchi di argomenti di D1 o D2.
L'applicazione in fase di compilazione di una conversione da un gruppo di metodi E a un tipo delegato D è descritta di seguito.
Questa riga non dice davvero nulla di interessante.
Si noti che l'esistenza di una conversione implicita da E a D non garantisce che l'applicazione in fase di compilazione della conversione avrà esito positivo senza errori.
Questa linea è affascinante. Significa che esistono conversioni implicite, ma che sono soggette a trasformarsi in errori! Questa è una regola bizzarra di C #. Per divagare un momento, ecco un esempio:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
Un'operazione di incremento è illegale in un albero delle espressioni. Tuttavia, il lambda è ancora convertibile nel tipo di albero delle espressioni, anche se la conversione viene mai utilizzata, è un errore! Il principio qui è che potremmo voler cambiare le regole di ciò che può andare in un albero delle espressioni in seguito; la modifica di tali regole non dovrebbe modificare le regole del sistema di tipo . Vogliamo costringerti a rendere i tuoi programmi inequivocabili ora , in modo che quando in futuro modificheremo le regole per gli alberi delle espressioni per migliorarli, non introdurremo modifiche sostanziali nella risoluzione del sovraccarico .
Comunque, questo è un altro esempio di questa sorta di regola bizzarra. Può esistere una conversione ai fini della risoluzione del sovraccarico, ma essere un errore da utilizzare effettivamente. Anche se in realtà non è esattamente la situazione in cui ci troviamo qui.
Andare avanti:
Viene selezionato un singolo metodo M corrispondente a una invocazione di metodo della forma E (A) [...] La lista di argomenti A è una lista di espressioni, ciascuna classificata come variabile [...] del parametro corrispondente nel modulo formale -parameter-list di D.
OK. Quindi eseguiamo la risoluzione del sovraccarico su X rispetto a D1. L'elenco dei parametri formali di D1 è vuoto, quindi eseguiamo la risoluzione dell'overload su X () e joy, troviamo un metodo "string X ()" che funziona. Allo stesso modo, l'elenco dei parametri formali di D2 è vuoto. Ancora una volta, troviamo che "string X ()" è un metodo che funziona anche qui.
Il principio qui è che la determinazione della convertibilità del gruppo di metodi richiede la selezione di un metodo da un gruppo di metodi usando la risoluzione dell'overload e la risoluzione dell'overload non considera i tipi restituiti .
Se l'algoritmo [...] produce un errore, si verifica un errore in fase di compilazione. Altrimenti l'algoritmo produce un unico metodo migliore M avente lo stesso numero di parametri di D e la conversione è considerata esistente.
C'è solo un metodo nel gruppo di metodi X, quindi deve essere il migliore. Abbiamo dimostrato con successo che esiste una conversione da X a D1 e da X a D2.
Ora, questa linea è pertinente?
Il metodo selezionato M deve essere compatibile con il tipo di delegato D, altrimenti si verifica un errore in fase di compilazione.
In realtà no, non in questo programma. Non arriviamo mai all'attivazione di questa linea. Perché, ricorda, quello che stiamo facendo qui è cercare di eseguire la risoluzione del sovraccarico su Y (X). Abbiamo due candidati Y (D1) e Y (D2). Entrambi sono applicabili. Quale è meglio ? In nessun punto delle specifiche descriviamo la migliore tra queste due possibili conversioni .
Ora, si potrebbe certamente sostenere che una conversione valida è migliore di una che produce un errore. Ciò significherebbe quindi effettivamente, in questo caso, che la risoluzione del sovraccarico prende in considerazione i tipi restituiti, che è qualcosa che vogliamo evitare. La domanda quindi è quale principio sia migliore: (1) mantenere l'invariante che la risoluzione del sovraccarico non considera i tipi restituiti, o (2) provare a scegliere una conversione che sappiamo funzionerà su una che sappiamo non lo farà?
Questa è una chiamata di giudizio. Con lambdas , lo facciamo prendere in considerazione il tipo di ritorno in questi tipi di conversioni, nella sezione 7.4.3.3:
E è una funzione anonima, T1 e T2 sono tipi delegati o tipi di albero delle espressioni con elenchi di parametri identici, esiste un tipo di ritorno dedotto X per E nel contesto di tale elenco di parametri e vale uno dei seguenti:
È un peccato che le conversioni del gruppo di metodi e le conversioni lambda siano incoerenti sotto questo aspetto. Tuttavia, posso conviverci.
Ad ogni modo, non abbiamo una regola "migliore" per determinare quale conversione è migliore, X in D1 o X in D2. Pertanto diamo un errore di ambiguità sulla risoluzione di Y (X).