2015-08-13 18 views
5

Mam nadzieję, że ktoś może mi to wyjaśnić. Przepraszam, jeśli jest to powtarzać, słowa kluczowe, aby wyjaśnić, co widzę są poza mną teraz ..C# Call jest niejednoznaczne podczas przekazywania grupy metod jako delegat

tu jest jakiś kod, który kompiluje

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)); 
    } 
} 

i tu jest jakiś kod, który nie

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; 
    } 
} 

Dodanie przeciążenia konstruktora powoduje najwyraźniej niejasności, ale nie jestem pewien dlaczego. Math.Sqrt nie jest przeciążony i wyraźnie ma typ powrotu double, a nie Task < double>.

Tutaj jest błąd:

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>>)'

Może ktoś wyjaśnić, dlaczego wybór nie jest oczywisty dla kompilatora?


łatwe obejście dla tych, którzy dbają:

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

Przy okazji jest inne obejście: 'nowy Transformator ((Func ) Math.Sqrt);' – Szer

+1

Może to być błąd. Osiem razy sam byłem na granicy wnioskowania typu i widziałem, że zawodzi podobnie. Powiem ci, co jest naprawdę okropne - nie kombinujesz połączeń konstruktora. Tut tut. 'public Transformer (Func actor): this (input => Task.Run (() => actor (input)))'. Wątpliwości, które naprawią, ale zapobiegną niefortunnym błędom! – Will

+0

@Will fair comment choć jest to (oczywiście mam nadzieję) proste repro. Rzeczywiście widzę to zachowanie w Microsoft Dataflow (https://msdn.microsoft.com/en-us/library/hh228603 (v = vs.110) .aspx) klas: TransformBlock <,> itp ... – eisenpony

Odpowiedz

5

mieć nieznaczny błędnej interpretacji tego, jak Func<Tin, Tout> prac. Spójrz na the docs:

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

Pierwszy argument jest parametrem i ostatni argument jest typem powrotu.

Kiedy patrzysz na tej uproszczonej wersji kodu:

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) 
    { 
    } 
} 

Można zauważyć, że oba argumenty najpierw określić double, co jest argumentem, a następnie różnią Zwraca typ: z T vs Task<T>.

Jednak, jak oboje wiemy: przeciążanie odbywa się na podstawie nazwy metody, arytmetyczności parametru i typów parametrów. Typy zwrotu są całkowicie ignorowane. W naszym przypadku oznacza to, że mamy dwa argumenty: Func<Tin, Tout> z double jako argument oraz vs Task<T> jako typ zwracany.

Przełączanie argumenty wokół kompiluje dobrze:

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

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

Jeśli będziesz wyglądać w Visual Studio można zauważyć, że metoda ta jest obecnie nieaktywna, co ma sens, ponieważ argument jest typu Methoddouble i jako taki zawsze będzie pasował T, a nie Task<T>.

Więc w celu sprawdzenia, że ​​to teraz hit poprawnego przeciążenie jeśli przechodzą w inny, asynchroniczny metody, można dodać w drugim sposobem:

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

i wywołać ją przy użyciu

new MyClass<double, double>(MethodAsync); 

Teraz zauważysz, że trafienie asynchroniczne Func<Task<T>, U>> (które możesz sprawdzić, wykonując proste drukowanie na konsoli od konstruktorów).


W skrócie: próbujesz wykonać rozdzielczość przeciążania dla typu zwrotu, co oczywiście nie jest możliwe.

+0

Hmm ... Nadal nie dostaję czegoś. Niejednoznaczność dotyczy konstruktorów, którzy mają różne parametry. Nie ma dwuznaczności dla Math.Sqrt - jest tylko jedna opcja. Na podstawie Twojej odpowiedzi, czy nie powinno to skutkować poprawną rozdzielczością? – eisenpony

+0

@eisenpony: niejednoznaczności nie ma w Math.Sqrt - w konstruktorze jest ich parametr Func. Mówisz, że przyjmują różne parametry, ale nie: akceptują inny typ zwrotu, ale te same argumenty. Jest to przypadek, w którym wydaje się, że konstruktory biorą różne parametry, ale musisz wyglądać głębiej. Najpierw pasuje do Funca i podwójnego i znajduje to. Następnie wyszukuje wewnątrz Func, aby zobaczyć, który z nich musi użyć i znajdzie 2 Funcs, które mają te same argumenty, ale inny typ powrotu, więc utknął tam. –

+0

Okej, dostaję cię. Nie sugerowałem, że Func przyjmuje różne parametry, myślałem, że konstruktorzy przyjmują różne parametry. Jednak to, co mówisz o istocie Func, z POV kompilatora, nieodróżnialne od siebie ma sens. Jest to normalne zachowanie kompilatora, ponieważ podczas normalnego wywoływania metod nie można przewidzieć oczekiwanego typu zwracanego. Jednak w tym przypadku wygląda na to, że kompilator mógł użyć typu zwracanego jako części jego rozdzielczości, ponieważ istnieje tylko jeden możliwy typ zwracany z grupy Math.Sqrt.method: double. – eisenpony

Powiązane problemy