37

I was implementing sync/async overloads when I came across this peculiar situation:

When I have a regular lambda expression without parameters or a return value it goes to the Run overload with the Action parameter, which is predictable. But when that lambda has a while (true) in it it goes to the overload with the Func parameter.

public void Test()
{
    Run(() => { var name = "bar"; });
    Run(() => { while (true) ; });
}

void Run(Action action)
{
    Console.WriteLine("action");
}

void Run(Func<Task> func) // Same behavior with Func<T> of any type. 
{
    Console.WriteLine("func");
}

Output:

action
func

So, how can that be? Is there a reason for it?

svick
  • 236,525
  • 50
  • 385
  • 514
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 1
    Interesting. It looks like you get the same behaviour for any `Func` type, too. I tried `Func` and `Func` with the same result. – Andrew Cooper Jun 19 '14 at 21:10
  • 2
    @L.B He's asking about overload resolution. He realizes that he isn't calling the delegate. – Tyler Jun 19 '14 at 21:11
  • @AndrewCooper Yes, I tried that too. I left it with `Task` because it came up in an `async-await` context – i3arnon Jun 19 '14 at 21:12
  • @AndrewCooper But not if `Run` is generic, `Run(Func func)` produces `action`. – p.s.w.g Jun 19 '14 at 21:12
  • 1
    Odd. Change it to `while (false) ;`, and it's treated as an `Action`. – Jim Mischel Jun 19 '14 at 21:16
  • 1
    ...wow. I guess while all `void`s are created equal, some are more equal than others. – user541686 Jun 19 '14 at 21:17
  • 3
    Get rid of the overload resolution piece. The question can be simplified to what makes `Func func = () => { while(true) ; };` compile. – Anthony Pegram Jun 19 '14 at 21:17
  • 1
    I see the same results with other forms of infinite loop, e.g. `for (;true;) ;` produces `func`, but `{ for (var i=0; i<9; i++) ; }` produces `action`. But `{ while (true) ; return; }` produces `action`! – p.s.w.g Jun 19 '14 at 21:17
  • 2
    @AnthonyPegram: No, overload resolution is important. If you take out the `Func` overload it still compiles (prints "action" x 2), but if you take out the `Action` one it doesn't. So there is something that makes the infinite loop prefer the `Func` overload over the `Action` one. – Jon Jun 19 '14 at 21:21
  • 1
    Same behavior with .NET 3.5 having no `async` `await` and no `Task` (I used a dummy empty `public class Task { }`) – Olivier Jacot-Descombes Jun 19 '14 at 21:21
  • 1
    [Lambda conversions with unclear return type and overload resolution](http://stackoverflow.com/questions/11941779/lambda-conversions-with-unclear-return-type-and-overload-resolution), [Lamdba of “x => { throw .. }” inferred to match Func in overloaded method?](http://stackoverflow.com/questions/23486452/lamdba-of-x-throw-inferred-to-match-funct-task-in-overloaded-metho). – CodeCaster Jun 19 '14 at 21:23

1 Answers1

29

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>.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 1
    +1, this is it. I was just coming back here having read the equivalent section 7.5.3.3 of the C# 4.0 spec (which version do you quote?). Kudos. – Jon Jun 19 '14 at 21:27
  • @Jon I think 3.0, because it's the one I found on my computer first. – Servy Jun 19 '14 at 21:28
  • You mean "... in which there are no code paths that return the correct value ..." – Olivier Jacot-Descombes Jun 19 '14 at 21:32
  • @Servy I know it's a stretch, but do you know what's behind that decision? Why is "void returning" a lesser candidate? Or is it just arbitrary? – i3arnon Jun 19 '14 at 22:04
  • 1
    @I3arnon You'd have to ask whomever made the decision why they made it. – Servy Jun 20 '14 at 14:04
  • 13
    @I3arnon: The people who made the decision are a fair-sized group but the person who implemented that decision was me. The reasoning is: if you have `()=>X()` where `X()` returns a value, odds are good that you meant to *use* that value; if you meant to ignore it you would have written `()=>{X();}`. So if there is a choice between "use the value" and "ignore the value", we choose "use the value" -- we choose `Func` over `Action` in those cases where it is ambiguous. – Eric Lippert Jun 23 '14 at 13:48
  • @I3arnon I don't understand (still) how `{ while (true) ; }` produce a value ( hence fits better to Func)...? it's just a loop.... can you show me the missing part ? – Royi Namir Jul 14 '14 at 07:55
  • @RoyiNamir IIUC, the point isn't that it produces a value, it's that it **doesn't fail** to produce a value (for all code paths). That means that it **can** be a `Func`, and `Func` is preferred. For anything deeper you would need to direct a question @EricLippert... – i3arnon Jul 14 '14 at 08:06
  • @RoyiNamir There is no requirement that all methods that have a return value must return a value. The requirement is that all code paths that reach the "end" of the method must return an appropriate value. If the method body is `while(true);` then all 0 of 0 code paths return the correct type of value. – Servy Jul 14 '14 at 14:06
  • @Servy Wait , why isnt this loop is a code path ? (or maybe i dont know what is code path is - VS other code ? ) – Royi Namir Jul 15 '14 at 10:08
  • 3
    @RoyiNamir It's not a code path because it doesn't result in leaving the method. There are no code paths in the method because there is no place where the code leaves the method. – Servy Jul 15 '14 at 13:40