Here's a complete example which doesn't involve Task
, to remove any hint of asynchrony being involved:
using System;
class Program
{
static void Method(Action action)
{
Console.WriteLine("Action");
}
static void Method(Func<int> func)
{
Console.WriteLine("Func<int>");
}
static void ThrowException()
{
throw new Exception();
}
static void Main()
{
// Resolvse to the Action overload
Method(() => ThrowException());
// Resolves to the Func<int> overload
Method(() => { throw new Exception(); });
}
}
Using section numbering from ECMA 334 (5th edition), we're interested in section 12.6.4 - overload resolution. The two important steps are:
- Identify applicable methods (12.6.4.2)
- Identify the best method (12.6.4.3)
We'll look at each call in turn
Call 1: () => ThrowException()
Let's start with the first call, which has an argument of () => ThrowException()
. To check for applicability, we need a conversion from that argument to either Action
or Func<int>
. We can check that without involving overloading at all:
// Fine
Action action = () => ThrowException();
// Fails to compile:
// error CS0029: Cannot implicitly convert type 'void' to 'int'
// error CS1662: Cannot convert lambda expression to intended delegate type because
// some of the return types in the block are not implicitly convertible to the
// delegate return type
Func<int> func = () => ThrowException();
The CS1662 error is a little unfortunately worded in this case - it's not that there's a return type in the block that's not implicitly convertible to the delegate return type, it's that there isn't a return type in the lambda expression at all. The spec way of preventing this is in section 11.7.1. None of the permitted conversions there work. The closest is this (where F is the lambda expression and D is Func<int>
):
If the body of F
is an expression, and either F
is non-async and D
has a non-void return type T
, or F
is async and D
has a return type Task<T>
, then when each parameter of F is given the type of the corresponding parameter in D
, the body of F
is a valid expression (w.r.t §12) that is implicitly convertible to T
.
In this case the expression ThrowException
is not implicitly convertible to int
, hence the error.
All of this means that only the first method is applicable for () => ThrowException()
. Our pick for "best function member" is really easy when the set of applicable function members only has a single entry...
Call 2: () => { throw new Exception(); }
Now let's look at the second call, which has () => { throw new Exception(); }
as the argument. Let's try the same conversions:
// Fine
Action action = () => { throw new Exception(); };
// Fine
Func<int> func = () => { throw new Exception(); };
Both conversions work here. The latter one works because of this bullet from 11.7.1:
If the body of F
is a statement block, and either F
is non-async and D
has a
non-void return type T
, or F
is async and D
has a return type Task<T>
,
then when each parameter of F
is given the type of the corresponding parameter in
D
, the body of F
is a valid statement block (w.r.t §13.3) with a nonreachable
end point in which each return statement specifies an expression that is implicitly
convertible to T
.
I realize it sounds odd that this works, but:
- The end point of the block is not reachable
- There are no return statements, so the condition of "each return statement specifies [...]" is indeed met
To put it another way: you could use that block as the body of a method that's declared to return int
.
That means both our methods are applicable in this case.
So which is better?
Now we need to look at section 12.6.4.3 to work out which method will actually be picked.
There are lots of rules here, but the one that decides things here is the conversion from the lambda expression to either Action
or Func<int>
. That's resolved in 12.6.4.4 (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 is a better conversion target than D2
- D1 and D2 have identical parameter lists, and one of the following holds:
- D1 has a return type Y1, and D2 has a return type Y2, an inferred return type X exists for E in the context of that parameter list (§12.6.3.13), and the conversion from X to Y1 is better than the conversion from X to Y2
- E is async [... - skipped because it's not]
- D1 has a return type Y, and D2 is void returning
The part I've put in bold is the important bit. When you consider the following scenario:
- E is
() => { throw new Exception(); }
- T1 is
Func<int>
(so D1 is Func<int>
too)
- T2 is
Action
(so D2 is Action
too)
... then both D1 and D2 have empty parameter lists, but D1 has a return type int
, and D2 is void returning.
Therefore the conversion to Func<int>
is better than the conversion to Action
... which means that Method(Action)
is a better function member than Member(Func<int>)
for the second call.
Phew! Don't you just love overload resolution?