2015-08-13 7 views
5

Hoffentlich kann mir jemand das erklären. Sorry, wenn es eine Wiederholung ist, um die Schlüsselwörter zu erklären, was ich sehe über mir sind jetzt ..C# Aufruf ist mehrdeutig beim Übergeben einer Methodengruppe als Stellvertreter

hier ist ein Code, der

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

kompiliert und hier ist ein Code, der nicht

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

Durch das Hinzufügen der Konstruktorüberladung erzeugt dies offensichtlich Mehrdeutigkeit, aber ich bin mir nicht sicher warum. Math.Sqrt ist nicht überladen und hat eindeutig einen Rückgabetyp von double, nicht Task < double>. Hier

ist der Fehler:

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

Kann jemand erklären, warum die Wahl auf den Compiler nicht offensichtlich ist?


einfache Abhilfe für diejenigen, die Pflege:

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

Btw gibt es eine andere Abhilfe: 'neue Transformer ((Func ) Math.Sqrt);' – Szer

+1

Könnte ein Fehler sein. Ich war selbst schon einige Male an der Grenze der Typinferenz und habe es ähnlich scheitern sehen. Ich werde dir sagen, was wirklich schrecklich ist - du verbindest deine Konstruktor-Anrufe nicht miteinander. Tut Tut. 'öffentliche Transformer (Func actor): dies (input => Task.Run ((=) (Akteur (Eingabe))). Zweifel, das würde beheben, aber es wird unglückliche Bugs verhindern! – Will

+0

@Will fair Kommentar obwohl dies (offensichtlich hoffe ich) eine einfache Repro. Ich sehe dieses Verhalten tatsächlich in den Microsoft Dataflow-Klassen (https://msdn.microsoft.com/en-us/library/hh228603 (v = vs.110) .aspx): TransformBlock <,> etc ... – eisenpony

Antwort

5

Sie haben eine leichte Fehlinterpretation, wie Func<Tin, Tout> funktioniert. Werfen Sie einen Blick auf the docs:

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

Das erste Argument ist ein Parameter, und das letzte Argument ist der Rückgabetyp.

Wenn man sich diese vereinfachte Version des Codes:

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

Sie werden feststellen, dass beide Argumente zuerst die double angeben, das ein Argument ist, und dann in den Rückgabetyp unterscheiden: die T vs Task<T>.

Wie wir beide wissen: Die Überladung erfolgt auf der Grundlage von Methodennamen, Parameterarten und Parametertypen. Rückgabetypen werden vollständig ignoriert. In unserem Fall heißt das, wir haben zwei Func<Tin, Tout> mit double als Argument und T vs Task<T> als Rückgabetyp.

Umschalten der Argumente kompiliert um gerade fein:

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

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

Wenn Sie in Visual Studio aussehen werden Sie feststellen, dass diese Methode nun ausgegraut ist, was Sinn macht, weil das Argument Method vom Typ double und entspricht daher immer T und nicht Task<T>.

Also, um zu testen, ob es nun die korrekte Überlastung treffen würde, wenn Sie in einer anderen, asynchronen Methode übergeben, können Sie in einem zweiten Verfahren hinzufügen:

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

und nennen Sie es mit

new MyClass<double, double>(MethodAsync); 

Sie werden jetzt feststellen, dass die asynchrone Func<Task<T>, U>> ist (die Sie überprüfen können, indem Sie einfach einen Druck auf Konsole von den Konstruktoren).

Kurz gesagt: Sie versuchen, eine Überladungsauflösung für einen Rückgabetyp durchzuführen, was offensichtlich nicht möglich ist.

+0

Hmm .. Ich bekomme immer noch nichts. Die Mehrdeutigkeit betrifft die Konstruktoren, die unterschiedliche Parameter haben. Es gibt keine Mehrdeutigkeit für Math.Sqrt - es gibt nur eine Option. Basiert Ihre Antwort nicht auf einer korrekten Auflösung? – eisenpony

+0

@eisenpony: die Ambiguität ist nicht in Math.Sqrt - es ist in den Konstruktoren ihre Func-Parameter. Sie sagen, sie nehmen andere Parameter, aber sie nicht: sie akzeptieren einen anderen Rückgabetyp, aber die gleichen Argumente. Dies ist ein Fall, in dem es so aussieht, als würden die Konstrukteure unterschiedliche Parameter verwenden, aber Sie müssen genauer hinsehen. Zuerst passt es für ein Func und ein Doppel und es findet das. Dann sieht es innerhalb der Func, um zu sehen, welche es zu verwenden hat und es findet 2 Funcs, die dieselben Argumente aber einen anderen Rückgabetyp haben, so dass es dort festsitzt. –

+0

Okay, ich verstehe dich. Ich habe nicht vorgeschlagen, dass der Func verschiedene Parameter annimmt, ich dachte, die Konstrukteure würden andere Parameter nehmen. Was man jedoch über das POV des Compilers sagt, ist nicht sinnvoll. Dies ist das normale Verhalten des Compilers, da es beim normalen Aufrufen von Methoden nicht möglich ist, den erwarteten Rückgabetyp vorauszusehen. In diesem Fall fühlt es sich jedoch so an, als hätte der Compiler _could_ den Rückgabetyp als Teil seiner Auflösung verwendet, da es nur einen möglichen Rückgabetyp aus der Math.Sqrt.method-Gruppe gibt: double. – eisenpony

Verwandte Themen