2014-05-06 7 views
5

Ich verstehe nicht, warum in dem folgenden LINQPad Code eine falsche Erweiterung Methode C# endet Ausführung:Lambda von "x => {throw ..}" abgeleitet, um Func <T,Task> in überladener Methode übereinzustimmen?

void Main() 
{ 
    // Actual: Sync Action 
    "Expected: Sync Action".Run(x => { x.Dump(); }); 

    // Actual: Async Task 
    "Expected: Async Task".Run(async x => { await System.Threading.Tasks.Task.Run(() => x.Dump()); }); 

    // Actual: Async Task!! 
    "Expected: Sync Action".Run(x => { throw new Exception("Meh"); }); 
} 

static class Extensions 
{ 
    public static void Run<T>(this T instance, Action<T> action) 
    { 
     "Actual: Sync Action".Dump(); 
     action(instance); 
    } 

    public static void Run<T>(this T instance, Func<T, System.Threading.Tasks.Task> func) 
    { 
     "Actual: Async Task".Dump(); 
     func(instance).Wait(); 
    } 
} 

Warum der Compiler denkt, dass das Lambda hier eine Aufgabe zurückkehrt?

Ich erwartete "Actual: Sync Action" im dritten Aufruf von Run(), da nichts im Lambda angibt, dass dies eine Func-Aufgabe ist.

+0

Dieses IST LINQPad Code .. Nichts Besonderes, außer für den Dump() Erweiterungsmethode zurück. – ichen

+1

Sie haben recht, es hat eigentlich nichts mit der Erweiterungsmethode zu tun, da ich mit normal überladenen Methoden reproduzieren konnte. – ichen

Antwort

2

Dies ist einfach ein Problem mit der Überladungsauflösung. Natürlich kann das Lambda x => { throw new Exception("Meh"); } entweder zu einem Action<T> oder zu einem Func<T, SomeNonVoidType> (sowie zu vielen anderen Delegatentypen, die für diese Frage irrelevant sind) konvertiert werden. Es sind einfach die Überladungsauflösungsregeln von C#, die letzteres in diesem Fall bevorzugen.

Hier ist ein repräsentatives Beispiel:

void Main() 
{ 
    // Output: Func<T, int> 
    "Test".WhatsThis(x => { throw new Exception("Meh"); }); 
} 

static class Extensions 
{ 
    public static void WhatsThis<T>(this T dummy, Action<T> action) 
    { 
     "Action<T>".Dump(); 
    } 
    public static void WhatsThis<T>(this T dummy, Func<T, int> func) 
    { 
     "Func<T, int>".Dump(); 
    } 
} 

Was warum dies den Fall ist, ich bin nicht 100% sicher, aber ein Casual Blick auf den spec zeigt mir die unten wahrscheinliche Erklärung (Schwerpunkt von mir):

7.5.3 Überlast Auflösung

[...]

7.5.3.3 Bessere Umwandlung von Ausdruck

Bei einer impliziten Konvertierung C1, die von einem Ausdruck E in einen Typ T1 konvertiert, und einer impliziten Konvertierung C2, die von einem Ausdruck E in einen Typ T2 konvertiert, ist C1 eine bessere Konvertierung wenn mindestens eine der folgenden als C2 gilt:

[...]

• E eine anonyme Funktion ist, ist entweder ein T1 Delegattyp D1 oder ein Ausdruck Baumtyp Expression<D1>, T2 ist entweder ein Delegiertyp D2 oder ein Ausdrucksbaum Typ Expression<D2> und einer der folgenden Werte:

[...]

• D1 einen Rückgabetyp Y hat, und D2 ist nichtig

+0

Danke für die Erklärung, was passieren könnte. Im zweiten Aufruf oben spezifiziere ich explizit "async x =>", was auf den Compiler hinweist, dass es sich hier um eine Lambda-Rückgabe handelt. Ist es in Ermangelung des async-Schlüsselworts sinnvoll, anstelle der Aktion die Funktion "default" zu verwenden oder ist es nur eine willkürliche Wahl? – ichen

+0

Offensichtlich denkt die Spezifikation in diesem Zusammenhang an einen nicht-void zurückkehrenden Delegierten als "besser"/"spezifischer" als an einen leeren. Es gibt jedoch keinen Hinweis darauf, warum. Ich vermute, dass die annotierte Spezifikation mehr verraten könnte (ich habe leider keine Kopie). Wenn jemand wie Eric Lippert diese Frage sieht, sind die Chancen gut, dass wir eine definitive Erklärung bekommen. – Ani

+0

@ichen: Das 'async' Schlüsselwort beeinflusst nicht den Typ des Lambda.Ich glaube, dass die Absicht der Überladungsauflösungsregeln ist, 'async void' lambdas zu vermeiden, [welche besonders schwierig sind] (http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx). –

Verwandte Themen