Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda of "x => { throw .. }" inferred to match Func<T,Task> in overloaded method?

I don't understand why C# ends up executing an incorrect extension method in the following LINQPad code:

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

Why does the compiler think that the lambda returns a Task here?

I expected to see "Actual: Sync Action" in the third call to Run() since nothing in the lambda indicates that this is a Func returning Task.

like image 719
ichen Avatar asked Nov 01 '22 00:11

ichen


1 Answers

This is simply an overload resolution issue. Clearly, the lambda x => { throw new Exception("Meh"); } can be converted to either an Action<T> or to a Func<T, SomeNonVoidType> (as well as to many other delegate types irrelevant to this question). It's simply C#'s overload resolution rules that prefer the latter in this case.

Here's a more representative example:

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

As for why this is the case, I'm not 100% sure, but a casual look at the spec shows me the below likely explanation (emphasis mine):

7.5.3 Overload resolution

[...]

7.5.3.3 Better conversion from expression

Given an implicit conversion C1 that converts from an expression E to a type T1, and an implicit conversion C2 that converts from an expression E to a type T2, C1 is a better conversion than C2 if at least one of the following holds:

[...]

• E is an anonymous function, T1 is either a delegate type D1 or an expression tree type Expression<D1>, T2 is either a delegate type D2 or an expression tree type Expression<D2> and one of the following holds:

[...]

• D1 has a return type Y, and D2 is void returning

like image 164
Ani Avatar answered Nov 08 '22 04:11

Ani