2014-05-06 10 views
5

Nie rozumiem dlaczego C# kończy się wykonaniem nieprawidłową metodę rozszerzenia w poniższym kodzie LINQPad:Lambda z "x => {rzut ..}" wywnioskowano, aby dopasować Func <T,Task> w przeciążonej metodzie?

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

Dlaczego kompilator że lambda zwraca zadanie tutaj?

Oczekiwano, że w trzecim wywołaniu funkcji Run() pojawi się komunikat "Actual: Sync Action", ponieważ nic w lambda nie oznacza, że ​​jest to zadanie zwracające Func.

+0

Jest to kod LINQPad .. Nic specjalnego z wyjątkiem Dump() metodę rozszerzenia. – ichen

+1

Masz rację, w rzeczywistości nie ma ona związku z metodą rozszerzenia, ponieważ udało mi się ją odtworzyć przy użyciu normalnie przeładowanych metod. – ichen

Odpowiedz

2

To jest po prostu problem z przeciążeniem. Oczywiście, lambda x => { throw new Exception("Meh"); } może zostać przekonwertowana na Action<T> lub na Func<T, SomeNonVoidType> (jak również na wiele innych typów delegatów niezwiązanych z tym pytaniem). To po prostu reguły przeładowania C#, które preferują ten drugi w tym przypadku.

Oto bardziej reprezentatywny przykład:

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

chodzi o dlaczego jest to przypadek, nie jestem w 100% pewien, ale dorywczo spojrzenie na spec pokazuje mi poniżej prawdopodobnym wyjaśnieniem (podkreślenie moje):

7.5.3 rozdzielczości przeciążenia

[...]

7.5.3.3 Lepsza konwersja z wyrażenia

Biorąc pod uwagę niejawną konwersję C1, która przekształca się z wyrażenia E na typ T1 i niejawną konwersję C2, która przekształca się z wyrażenia E na typ T2, C1 jest lepszą konwersją od C2, gdy co najmniej jeden z poniższych warunków:

[...]

• E anonimowy funkcja T1 albo D1 typu przedstawiciel lub typ drzewa ekspresji Expression<D1>, T2 albo delegat typu D2 lub drzewa wyrażeń typ Expression<D2> i jeden z następujących chwytów:

[...]

• D1 ma typ zwracanej Y i D2 jest nieważna powrocie

+0

Dziękuję za wyjaśnienie, co może się dziać. W drugim wywołaniu powyżej wyraźnie określam "async x =>", co może wskazywać kompilatorowi, że mamy tu do czynienia z zadaniem Zwrotu lambda. Czy w przypadku braku asynchronicznego słowa kluczowego sensowne jest "domyślne" ustawienie func zamiast akcji, czy jest to tylko arbitralny wybór? – ichen

+0

Najwyraźniej spec uważa, że ​​powracający delegat bez próżni jest "lepszy"/"bardziej konkretny" niż pusta w tym kontekście. Nie daje jednak żadnych wskazówek, dlaczego tak jest. Podejrzewam, że opisana specyfikacja może ujawnić więcej (niestety nie mam kopii). Jeśli ktoś taki jak Eric Lippert widzi to pytanie, szanse są dobre, abyśmy mogli uzyskać bardziej jednoznaczne wyjaśnienie. – Ani

+0

@ichen: Słowo kluczowe 'async' nie ma wpływu na typ lambda.Uważam, że intencją zasad dotyczących przeładowania jest unikanie 'async void' lambdas, [które są szczególnie trudne] (http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx). –