Peculiar overload resolution with while (true)

So to start with, the first expression can only possibly call the first overload. It is not a valid expression for a Func<Task> because there is a code path that returns an invalid value (void instead of Task).

() => while(true) is actually a valid method for either signature. (It, along with implementations such as () => throw new Expression(); are valid bodies of methods that return any possible type, including void, an interesting point of trivia, and why auto generated methods from an IDE typically just throw an exception; it’ll compile regardless of the signature of the method.) A method that loops infinitely is a method in which there are no code paths that don’t return the correct value (and that’s true whether the “correct value” is void, Task, or literally anything else). This is of course because it never returns a value, and it does so in a way that the compiler can prove. (If it did so in a way that the compiler couldn’t prove, as it hasn’t solved the halting problem after all, then we’d be in the same boat as A.)

So, for our infinite loop, which is better, given that both overload are applicable. This brings us to our betterness section of the C# specs.

If we go to section 7.4.3.3, bullet 4, we see:

If E is an anonymous function, T1 and T2 are delegate types or expression tree types with identical parameter lists, and an inferred return type X exists for E in the context of that parameter list (ยง7.4.2.11):

[…]

if T1 has a return type Y, and T2 is void returning, then C1 is the better conversion.

So when converting from an anonymous delegate, which is what we’re doing, it will prefer the conversion that returns a value over one that is void, so it chooses Func<Task>.

Leave a Comment