考虑这个Reactive Extensions片段(忽略它的实用性):
return Observable.Create<string>(async observable =>
{
while (true)
{
}
});
这不能使用Reactive Extensions 2.2.5(使用NuGet Rx-Main包)进行编译。它失败了:
错误1以下方法或属性之间的调用不明确:&#39; System.Reactive.Linq.Observable.Create&lt; string&gt;(System.Func&lt; System.IObserver&lt; string&gt;,System.Threading.Tasks。任务&LT; System.Action&GT;&GT;)&#39;和&#39; System.Reactive.Linq.Observable.Create&lt; string&gt;(System.Func&lt; System.IObserver&lt; string&gt;,System.Threading.Tasks.Task&gt;)&#39;
但是,在while循环中的任何位置添加break
可修复编译错误:
return Observable.Create<string>(async observable =>
{
while (true)
{
break;
}
});
问题可以在没有Reactive Extensions的情况下重现(如果您想在不使用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>
{
}
忽略Reactive Extensions部分,为什么添加break
有助于C#编译器解决歧义?如何用C#规范中的重载决策规则来描述它?
我使用的是针对4.5.1的Visual Studio 2013 Update 2。
答案 0 :(得分:59)
最简单的就是在这里拉出async
以及lambdas,因为它强调了正在发生的事情。这两种方法都有效并将编译:
public static void Foo()
{
while (true) { }
}
public static Action Foo()
{
while (true) { }
}
然而,对于这两种方法:
public static void Foo()
{
while (true) { break; }
}
public static Action Foo()
{
while (true) { break; }
}
第一个编译,第二个没编译。它有一个代码路径,不会返回有效值。
实际上,while(true){}
(以及throw new Exception();
)是一个有趣的陈述,因为它是具有任何返回类型的方法的有效主体。
由于无限循环是两个重载的合适候选者,并且过载都不会更好&#34;因此会导致模糊错误。非无限循环实现在重载分辨率中只有一个合适的候选者,因此它编译。</ p>
当然,为了让async
重新发挥作用,它实际上在某种程度上与此相关。对于async
方法,它们始终返回某些内容,无论它是Task
还是Task<T>
。 &#34;更好&#34;当存在可以匹配的lambda时,重载决策的算法将优先选择返回值超过void
个委托的委托,但是在你的情况下,两个重载都有委托返回一个值,事实是{{1返回async
而不是Task
的方法在概念上等同于不返回值并不包含在该更好算法中。因此,即使两个重载都适用,非异步等效也不会导致歧义错误。
当然值得注意的是,编写一个程序来确定任意代码块是否会完成是一个着名的无法解决的问题,但是,虽然编译器无法正确评估每个代码片段是否都会完成,但它可以在某些简单的情况下,例如这个,证明代码实际上永远不会完成。因此,有一些编写代码的方法很明显(对你我来说)永远不会完成,但编译器会把它视为可能完成。
答案 1 :(得分:25)
离开async
开始......
使用break,lambda表达式的结尾是可到达的,因此lambda 的返回类型为void
。
没有中断,lambda表达式的结尾是不可达的,因此任何返回类型都是有效的。例如,这很好:
Func<string> foo = () => { while(true); };
虽然这不是:
Func<string> foo = () => { while(true) { break; } };
因此,如果没有break
,lambda表达式可以转换为具有单个参数的任何委托类型。使用break
,lambda表达式仅可转换为具有单个参数且返回类型为void
的委托类型。
添加async
部分,void
变为void
或Task
,vs void
,Task
或Task<T>
T
之前您可以拥有任何返回类型。例如:
// 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; } };