特殊的重载分辨率与while(true)

时间:2014-06-19 21:03:58

标签: c# .net lambda overloading overload-resolution

当我遇到这种奇怪的情况时,我正在实现同步/异步重载:

当我有一个没有参数或返回值的常规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

那么,怎么会这样呢?这有什么理由吗?

1 个答案:

答案 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