2015-08-13 17 views
5

Speriamo che qualcuno possa spiegarmelo. Scusate se è una ripetizione, le parole chiave per spiegare quello che sto vedendo sono al di là di me per ora ..C# La chiamata è ambigua quando si passa un gruppo di metodi come delegato

qui è un codice che compila

class Program 
{ 
    static void Main(string[] args) 
    { 
     new Transformer<double, double>(Math.Sqrt); 
    } 
} 

class Transformer<Tin, Tout> 
{ 
    Func<Tin, Task<Tout>> actor; 
    public Transformer(Func<Tin, Tout> actor) 
    { 
     this.actor = input => Task.Run<Tout>(() => actor(input)); 
    } 
} 

e qui è un codice che non lo fa

class Program 
{ 
    static void Main(string[] args) 
    { 
     new Transformer<double, double>(Math.Sqrt); 
    } 
} 

public class Transformer<Tin, Tout> 
{ 
    Func<Tin, Task<Tout>> actor; 
    public Transformer(Func<Tin, Tout> actor) 
    { 
     this.actor = input => Task.Run<Tout>(() => actor(input)); 
    } 

    public Transformer(Func<Tin, Task<Tout>> actor) 
    { 
     this.actor = actor; 
    } 
} 

Aggiungendo il sovraccarico del costruttore, questo crea apparentemente ambiguità ma non sono sicuro del perché. Math.Sqrt non è sovraccarico e ha chiaramente un tipo di ritorno doppio, non Task < double>.

Ecco l'errore:

The call is ambiguous between the following methods or properties: 'ConsoleApplication1.Transformer<double,double>.Transformer(System.Func<double,double>)' and 'ConsoleApplication1.Transformer<double,double>.Transformer(System.Func<double,System.Threading.Tasks.Task<double>>)'

qualcuno può spiegare perché la scelta non è ovvio per il compilatore?


soluzione facile per coloro che hanno a cuore:

class Program 
{ 
    static void Main(string[] args) 
    { 
     new Transformer<double, double>(d => Math.Sqrt(d)); 
    } 
} 
+2

Btw c'è un'altra soluzione: 'nuovo Transformer ((Func ) Math.Sqrt);' – Szer

+1

Potrebbe essere un bug. Sono stato al limite dell'inferenza di tipo un paio di volte me stesso, e ho visto fallire in questo modo. Ti dirò cos'è veramente terribile - non stai incatenando le chiamate del tuo costruttore. Tut tut 'public Transformer (Func actor): this (input => Task.Run (() => actor (input)))'. Dubbio che risolverebbe, ma preverrà bug infelici! – Will

+0

@Salvo commento equo anche se questo è (ovviamente, spero) una semplice riproduzione. In realtà sto vedendo questo comportamento nelle classi Microsoft Dataflow (https://msdn.microsoft.com/en-us/library/hh228603 (v = vs.110) .aspx): TransformBlock <,> ecc ... – eisenpony

risposta

5

Hai un leggero errore di interpretazione di come Func<Tin, Tout> opere. Date un'occhiata a the docs:

public delegate TResult Func<in T, out TResult>(
    T arg 
) 

Il primo argomento è un parametro e l'ultimo argomento è il tipo di ritorno.

Quando si guarda a questa versione semplificata del codice:

internal class Program 
{ 
    public static void Main(string[] args) 
    { 
     new MyClass<double, double>(Method); 
    } 

    private static double Method(double d) 
    { 
     throw new NotImplementedException(); 
    } 
} 


internal class MyClass<T, U> 
{ 
    public MyClass(Func<U, T> arg) 
    { 
    } 

    public MyClass(Func<U, Task<T>> arg) 
    { 
    } 
} 

Si noterà che entrambi gli argomenti prima specificano il double, che è un argomento, e quindi si differenziano per tipo di ritorno: il T vs Task<T>.

Tuttavia, come entrambi sappiamo: l'overloading viene eseguito in base al nome del metodo, al parametro arity e ai tipi di parametri. I tipi di ritorno sono completamente ignorati. Nel nostro caso ciò significa che abbiamo due Func<Tin, Tout> con double come argomento e T rispetto a Task<T> come tipo di reso.

commutazione gli argomenti intorno compila bene:

internal class MyClass<T, U> 
{ 
    public MyClass(Func<T, U> arg) 
    { 
    } 

    public MyClass(Func<Task<T>, U> arg) 
    { 
    } 
} 

Se ti aspetto in Visual Studio si noterà che questo metodo è ora in grigio, che ha un senso, perché l'argomento è di tipo Methoddouble e come tale corrisponderà sempre a T e non a Task<T>.

Quindi, al fine di verificare che sarebbe ora colpire il sovraccarico corretta se si passa in un altro, metodo asincrono, è possibile aggiungere in un secondo metodo:

private static double MethodAsync(Task<double> d) 
{ 
    throw new NotImplementedException(); 
} 

e chiamare utilizzando

new MyClass<double, double>(MethodAsync); 

Si noterà ora che viene eseguito il colpo asincrono Func<Task<T>, U>> (che è possibile verificare facendo semplicemente una stampa alla console dai costruttori).


In breve: si sta tentando di eseguire la risoluzione di sovraccarico su un tipo di reso, che ovviamente non è possibile.

+0

Hmm .. Non sto ancora ottenendo qualcosa. L'ambiguità è sui costruttori, che hanno parametri diversi. Non c'è ambiguità per Math.Sqrt - c'è solo un'opzione. In base alla risposta, non dovrebbe risultare in una risoluzione corretta? – eisenpony

+0

@eisenpony: l'ambiguità non è in Math.Sqrt - è nei costruttori il loro parametro Func. Dici che prendono parametri diversi ma non lo fanno: accettano un tipo di ritorno diverso, ma gli stessi argomenti. Questo è un caso in cui sembra che i costruttori assumano parametri diversi ma devi guardare più in profondità. Prima si abbina per un Func e un double e lo trova. Quindi guarda all'interno di Func per vedere quale deve essere utilizzato e trova 2 Func che hanno gli stessi argomenti ma un tipo di ritorno differente, quindi è bloccato lì. –

+0

Okay, ti prendo. Non stavo suggerendo che Func prendesse parametri diversi, pensavo che i costruttori avessero parametri diversi. Comunque, quello che dici sull'essere di Func, dal POV del compilatore, indistinguibile l'uno dall'altro ha senso. Questo è il comportamento normale per il compilatore perché quando si chiamano i metodi normalmente, non è possibile anticipare il tipo di reso previsto. Tuttavia, in questo caso sembra che il compilatore _could_ abbia usato il tipo restituito come parte della sua risoluzione perché esiste un solo tipo di ritorno possibile dal gruppo Math.Sqrt.method: double. – eisenpony

Problemi correlati