当我遇到这种奇怪的情况时,我正在实现同步/异步重载:
当我有一个没有参数或返回值的常规lambda表达式时,它会使用Run
参数转到Action
重载,这是可预测的。但是当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
那么,怎么会这样呢?这有什么理由吗?
答案 0 :(得分:26)
首先,第一个表达式只能调用第一个重载。它不是Func<Task>
的有效表达式,因为有一个代码路径返回无效值(void
而不是Task
)。
() => while(true)
实际上是任一签名的有效方法。 (它与诸如() => throw new Expression();
之类的实现一起是返回任何可能类型的方法的有效体,包括void
,一个有趣的琐事点,以及为什么来自IDE的自动生成方法通常只是抛出异常;无论方法的签名如何,它都会编译。)无限循环的方法是一种方法,其中没有代码路径不返回正确的值(而且“正确的值”是否为{{ 1}},void
或其他任何东西)。这当然是因为它从不返回一个值,并且它以编译器可以证明的方式这样做。 (如果它以编译器无法证明的方式这样做,因为它毕竟没有解决停止问题,那么我们将与Task
处于同一条船上。)
因此,对于我们的无限循环,这是更好的,因为两个过载都适用。这将我们带到了C#规范的更好部分。
如果我们转到第7.4.3.3节,第4章,我们会看到:
如果E是匿名函数,则T1和T2是具有相同参数列表的委托类型或表达式树类型,并且在该参数列表(第7.4.2.11节)的上下文中存在E的推断返回类型X:
[...]
如果T1的返回类型为Y,并且T2返回无效,那么C1是更好的转换。
因此,当从匿名委托转换时,我们正在做的事情是,它会更喜欢返回值为A
的转化,因此它会选择void
。