while(true)的独特重载分辨率
当遇到这种奇怪的情况时,我正在实现同步/asynchronous重载:
当我有一个没有参数或返回值的常规lambdaexpression式时,它会通过Action
参数进入Run
重载,这是可预测的。 但是,当lambda有一段while (true)
在它使用Func
参数进入重载。
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"); }
输出:
行动
FUNC
那么,怎么可能呢? 是有原因的吗?
所以首先,第一个expression式只能调用第一个重载。 它不是一个Func<Task>
的有效expression式,因为有一个返回无效值( void
而不是Task
)的代码path。
() => while(true)
实际上是签名的有效方法。 (它与诸如() => throw new Expression();
是有效的返回任何可能的types的方法体,包括void
,一个有趣的小问题,以及为什么自动生成的方法通常只是抛出一个exception;无论方法的签名如何,它都会被编译)无限循环的方法是没有代码path不返回正确值的方法(无论“正确值”是否为void
, Task
,或从字面上的其他任何东西)。 这当然是因为它永远不会返回一个值,而且它是以编译器可以certificate的方式进行的。 (如果这样做是因为编译器无法certificate,毕竟它还没有解决停止问题,那么我们就和A
。)
所以,对于我们的无限循环来说,这更好,因为这两个超载都是适用的。 这带来了我们的C#规范的更好的部分。
如果我们继续阅读第7.4.3.3节,第4项,我们看到:
如果E是匿名函数,则T1和T2是具有相同参数列表的委托types或expression式树型,并且在该参数列表(第7.4.2.11节)的上下文中存在针对E的推断返回typesX:
[…]
如果T1有返回typesY,T2返回无效,那么C1是更好的转换。
所以当从一个匿名委托转换,这就是我们正在做的,它会喜欢转换返回一个值是void
,所以它selectFunc<Task>
。