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

i3a*_*non 35 .net c# lambda overloading overload-resolution

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

当我有一个没有参数或返回值的常规lambda表达式时,它会Run使用Action参数进行重载,这是可预测的.但是当lambda中有一个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");
}
Run Code Online (Sandbox Code Playgroud)

输出:

动作
功能

那怎么可能呢?有原因吗?

Ser*_*rvy 26

首先,第一个表达式只能调用第一个重载.它不是a的有效表达式,Func<Task>因为存在返回无效值(void而不是Task)的代码路径.

() => while(true)实际上是任一签名的有效方法.(它,连同实现,比如() => throw new Expression();是返回任何可能的类型,包括方法的有效机构void,琐事的一个有趣的问题,为什么自动生成的方法,从一个IDE通常只是抛出一个异常,它会不顾编译的签名是无限循环的方法.)的方法是,其中有没有代码路径不返回正确的值(和"正确的价值"是否是这是真的的方法void,Task或从字面上别的).这当然是因为它永远不会返回值,并且它以编译器可以证明的方式执行.(如果它以编译器无法证明的方式这样做,因为它毕竟没有解决停止问题,那么我们将在同一条船上A.)

因此,对于我们的无限循环,这是更好的,因为两个过载都适用.这将我们带到了C#规范的更好部分.

如果我们转到第7.4.3.3节,第4章,我们看到:

如果E是匿名函数,T1和T2是委托类型或表达式树类型的具有相同的参数列表,并且推断的返回类型X在于参数列表(§7.4.2.11)的上下文中存在E:

[...]

如果T1的返回类型为Y,并且T2返回为空,那么C1是更好的转换.

因此,当从匿名委托进行转换时,这就是我们正在做的事情,它将更喜欢转换返回值的转换void,因此它选择Func<Task>.

  • @ I3arnon:做出决定的人是一个公平的小组,但实施该决定的人是我.理由是:如果你有`()=> X()`其中`X()`返回一个值,你想要*使用*那个值的几率是好的; 如果你想忽略它,你会写`()=> {X();}`.因此,如果在"使用值"和"忽略值"之间进行选择,我们选择"使用值" - 在不明确的情况下,我们选择`Func`而不是`Action`. (13认同)
  • @RoyiNamir这不是代码路径,因为它不会导致离开该方法.方法中没有代码路径,因为代码没有放置方法的地方. (3认同)