Perché Func ambiguo con Func <IEnumerable >?

Questo mi ha fatto impazzire, quindi ho pensato di chiederlo qui nella speranza che un guru del C # me lo spiegasse.

Perché questo codice genera un errore?

class Program { static void Main(string[] args) { Foo(X); // the error is on this line } static String X() { return "Test"; } static void Foo(Func<IEnumerable> x) { } static void Foo(Func x) { } } 

L’errore in questione:

 Error 1 The call is ambiguous between the following methods or properties: 'ConsoleApplication1.Program.Foo(System.Func<System.Collections.Generic.IEnumerable>)' and 'ConsoleApplication1.Program.Foo(System.Func)' C:\Users\mabster\AppData\Local\Temporary Projects\ConsoleApplication1\Program.cs 12 13 ConsoleApplication1 

Non importa quale sia il tipo che uso – se sostituisci le dichiarazioni “String” con “int” in quel codice otterrai lo stesso tipo di errore. È come se il compilatore non fosse in grado di distinguere tra Func e Func<IEnumerable> .

Qualcuno può fare luce su questo?

OK, ecco l’accordo.

La versione breve:

  • L’errore di ambiguità è, abbastanza bizzarramente, corretto.
  • Il compilatore C # 4 produce anche un errore spuria dopo l’errore di ambiguità corretto. Questo sembra essere un bug nel compilatore.

La versione lunga:

Abbiamo un problema di risoluzione del sovraccarico. La risoluzione del sovraccarico è estremamente ben specificata.

Fase 1: determinare il set candidato. Questo è facile. I candidati sono Foo(Func>) e Foo(Func) .

Fase 2: determinare quali membri del gruppo candidato sono applicabili . Un membro applicabile ha ogni argomento convertibile in ogni tipo di parametro.

Foo(Func>) applicabile? Bene, X è convertibile in Func ?

Consultiamo la sezione 6.6 delle specifiche. Questa parte della specifica è ciò che noi designer del linguaggio chiamiamo “davvero strano”. Fondamentalmente, dice che può esistere una conversione, ma l’uso di quella conversione è un errore . (Ci sono buone ragioni per cui abbiamo questa bizzarra situazione, per lo più avendo a che fare con l’evitamento di futuri cambi di rottura ed evitando situazioni di “gallina e uova”, ma nel tuo caso abbiamo un comportamento un po ‘sfortunato come risultato.)

Fondamentalmente, la regola qui è che una conversione da X a un tipo delegato senza parametri esiste se la risoluzione di sovraccarico su una chiamata del modulo X() avrà successo. Chiaramente una tale chiamata avrebbe avuto successo, e quindi esiste una conversione. L’ utilizzo effettivo di tale conversione è un errore perché i tipi di restituzione non corrispondono, ma la risoluzione di sovraccarico ignora sempre i tipi di restituzione .

Quindi, esiste una conversione da X a Func , e quindi quel sovraccarico è un candidato applicabile.

Ovviamente per lo stesso motivo anche l’altro sovraccarico è un candidato applicabile.

Fase tre: ora abbiamo due candidati applicabili. Qual è il migliore”?

Quello che è “migliore” è quello con il tipo più specifico . Se hai due candidati applicabili, M(Animal) e M(Giraffe) scegliamo la versione Giraffe perché una Giraffa è più specifica di un Animale. Sappiamo che la giraffa è più specifica perché ogni giraffa è un animale, ma non tutti gli animali sono una giraffa.

Ma nel tuo caso nessuno dei due tipi è più specifico dell’altro. Non c’è conversione tra i due tipi di Func.

Quindi nessuno dei due è migliore, quindi la risoluzione di sovraccarico segnala un errore.

Il compilatore C # 4 ha quindi quello che sembra essere un bug, in cui la sua modalità di ripristino degli errori sceglie comunque uno dei candidati e segnala un altro errore. Non mi è chiaro perché ciò stia accadendo. Fondamentalmente si sta dicendo che il recupero degli errori sta scegliendo il sovraccarico IEnumerable, e quindi si noti che la conversione del gruppo metodo produce un risultato insostenibile; vale a dire, quella stringa non è compatibile con IEnumerable .

L’intera situazione è piuttosto sfortunata; sarebbe stato meglio dire che non vi è alcuna conversione da metodo a gruppo a delegato se i tipi di ritorno non corrispondono. (Oppure, una conversione che produce un errore è sempre peggiore di una conversione che non lo fa). Tuttavia, ora siamo bloccati.

Un fatto interessante: le regole di conversione per lambda tengono conto dei tipi di ritorno. Se dici Foo(()=>X()) , facciamo la cosa giusta. Il fatto che lambda e gruppi di metodi abbiano regole di convertibilità diverse è piuttosto sfortunato.

Quindi, riassumendo, il compilatore è in realtà una corretta implementazione delle specifiche in questo caso, e questo particolare scenario è una conseguenza involontaria di alcune scelte specilmente sfortunate.

Il tuo codice richiede che “magic” avvenga due volte, una volta per convertire dal gruppo metodo indicato a un delegato e una volta per eseguire la risoluzione di sovraccarico.

Nonostante il fatto che tu abbia solo un metodo chiamato X , le regole del compilatore sono costruite per il caso quando ci sono più.

Inoltre, poiché i delegati non devono corrispondere esattamente alla firma del metodo, la complessità viene ulteriormente aumentata. Inoltre, qualsiasi metodo dato può essere convertito in un numero illimitato di diversi tipi di delegati con firme identiche.

Il tuo caso particolare sembra abbastanza semplice, ma il caso generale è molto difficile, quindi il linguaggio non lo consente.

Se fai parte del lavoro a mano, risolvi il problema. per esempio

 Func d = X; Foo(d); 

dovrebbe compilare bene.