How does adding a break in the while loop solve the overload problem ambiguously?

Consider this fragment of Reactive Extensions (ignore its practicality):

return Observable.Create<string>(async observable => { while (true) { } }); 

This does not compile with Reactive Extensions 2.2.5 (using the NuGet Rx-Main package). He does not work:

Error 1 The call is ambiguous between the following methods or properties: 'System.Reactive.Linq.Observable.Create <string> (System.Func <System.IObserver <string>, System.Threading.Tasks.Task <System.Action>)' and 'System.Reactive.Linq.Observable.Create <string> (System.Func <System.IObserver <string>, System.Threading.Tasks.Task>)'

However, adding break anywhere in the while loop fixes a compilation error:

 return Observable.Create<string>(async observable => { while (true) { break; } }); 

The problem can be reproduced without Reactive Extensions at all (easier if you want to try it without using Rx):

 class Program { static void Main(string[] args) { Observable.Create<string>(async blah => { while (true) { Console.WriteLine("foo."); break; //Remove this and the compiler will break } }); } } public class Observable { public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task> subscribeAsync) { throw new Exception("Impl not important."); } public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, Task<Action>> subscribeAsync) { throw new Exception("Impl not important."); } } public interface IObserver<T> { } 

Ignoring the Reactive Extensions part, why does adding break help the C # compiler resolve ambiguity? How can this be described with overload resolution rules according to the C # specification?

I am using Visual Studio 2013 Update 2 to target 4.5.1.

+48
c #
Sep 17 '14 at 19:12
source share
2 answers

The easiest way is to just pull out async as well as lambdas here, as it emphasizes what is happening. Both of these methods are valid and will be compiled:

 public static void Foo() { while (true) { } } public static Action Foo() { while (true) { } } 

However, for these two methods:

 public static void Foo() { while (true) { break; } } public static Action Foo() { while (true) { break; } } 

The first compiler, and the second not. It has a code path that does not return a valid value.

In fact, while(true){} (along with throw new Exception(); ) is an interesting statement in that it is the actual body of a method with any return type.

Since an infinite loop is a suitable candidate for both overloads, and neither overload is "better", this leads to an ambiguity error. An implementation without an infinite loop has only one suitable candidate for overload resolution, so it compiles.

Of course, to bring async back to the game, this is really true in one case. For async methods, they both always return something, whether Task or Task<T> . The brilliance algorithms for resolving overloads will be preferable to delegates that return a value over void delegates when there is a lambda that can match, but in your case two overloads have delegates that return a value, the fact is that for async , return Task instead of Task<T> , is the conceptual equivalent of a non-returning value, is not included in this survival algorithm. Because of this, the non-asynchronous equivalent will not lead to an ambiguity error, even if both overloads are applicable.

Of course, it is worth noting that writing a program to determine whether an arbitrary block of code will ever be completed is, however, an insoluble problem, although the compiler cannot correctly evaluate whether each individual fragment is complete, it can prove, in certain simple cases such as this one that the code will never actually complete. Because of this, there are ways to write code that explicitly (for you and for me) never ends, but that the compiler will consider completion possible.

+59
Sep 17 '14 at 19:19
source share

Leaving async out of this to start with ...

With a break, the end of the lambda expression is reachable, so the return type of the lambda must be void .

Without a break, the end of the lambda expression is unreachable, so any return type will be valid. For example, this is normal:

 Func<string> foo = () => { while(true); }; 

whereas it is not:

 Func<string> foo = () => { while(true) { break; } }; 

Thus, without break , the lambda expression will be converted to any type of delegate with a single parameter. Using break , a lambda expression is converted only to a delegate type with a single parameter and void return type.

Add the async part and void will become void or Task , vs void , Task or Task<T> for any T , where previously you could have any type of return. For example:

 // Valid Func<Task<string>> foo = async () => { while(true); }; // Invalid (it doesn't actually return a string) Func<Task<string>> foo = async () => { while(true) { break; } }; // Valid Func<Task> foo = async () => { while(true) { break; } }; // Valid Action foo = async () => { while(true) { break; } }; 
+24
Sep 17 '14 at 19:21
source share



All Articles