带有 while (true) 的特殊重载解决方案
Posted
技术标签:
【中文标题】带有 while (true) 的特殊重载解决方案【英文标题】:Peculiar overload resolution with while (true) 【发布时间】:2014-08-10 13:22:52 【问题描述】:当我遇到这种特殊情况时,我正在实现同步/异步重载:
当我有一个不带参数或返回值的常规 lambda 表达式时,它会使用 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<T>
类型也有相同的行为。我尝试了Func<int>
和Func<string>
,结果相同。
@L.B 他在询问重载解决方案。他意识到他没有给代表打电话。
奇数。将其更改为while (false) ;
,它被视为Action
。
摆脱重载解决方案。这个问题可以简化为是什么让Func<Any T> func = () => while(true) ; ;
编译。
@AnthonyPegram:不,重载解决方案很重要。如果你取出 Func
重载它仍然可以编译(打印“action” x 2),但如果你取出 Action
一个它不会。所以有一些东西使无限循环更喜欢Func
重载而不是Action
。
【参考方案1】:
所以首先,第一个表达式只能调用第一个重载。对于Func<Task>
,它不是一个有效的表达式,因为有一个返回无效值的代码路径(void
而不是Task
)。
() => while(true)
实际上是任一签名的有效方法。 (它与诸如 () => throw new Expression();
之类的实现一起是返回任何可能类型的有效方法体,包括 void
,这是一个有趣的琐事,以及为什么从 IDE 自动生成的方法通常只会抛出异常;它会无论方法的签名如何,都可以编译。)无限循环的方法是一种没有不返回正确值的代码路径的方法(无论“正确值”是void
,@987654328,都是如此@,或其他任何字面意思)。这当然是因为它从不返回一个值,并且它以编译器可以证明的方式这样做。 (如果它以编译器无法证明的方式这样做,因为它毕竟没有解决停机问题,那么我们将与A
在同一条船上。)
所以,对于我们的无限循环,这更好,因为这两个重载都适用。这将我们带到 C# 规范的更好部分。
如果我们转到第 7.4.3.3 节的第 4 条,我们会看到:
如果 E 是匿名函数,则 T1 和 T2 是具有相同参数列表的委托类型或表达式树类型,并且在该参数列表的上下文中存在 E 的推断返回类型 X(第 7.4.2.11 节):
[...]
如果 T1 有一个返回类型 Y,而 T2 是 void 返回,那么 C1 是更好的转换。
因此,当我们正在从匿名委托进行转换时,它会更喜欢返回值的转换而不是 void
,因此它选择 Func<Task>
。
【讨论】:
+1,就是这样。我刚回到这里阅读了 C# 4.0 规范的等效第 7.5.3.3 节(你引用哪个版本?)。荣誉。 @Jon 我认为是 3.0,因为它是我首先在我的电脑上找到的。 @I3arnon 你必须问问做出决定的人为什么做出决定。 @I3arnon:做出决定的人是一个相当大的群体,但执行该决定的人是我。原因是:如果您有()=>X()
其中 X()
返回一个值,那么您打算使用该值的可能性很大;如果你打算忽略它,你会写()=>X();
。因此,如果在“使用价值”和“忽略价值”之间进行选择,我们选择“使用价值”——在不明确的情况下,我们选择 Func
而不是 Action
。
@RoyiNamir 这不是代码路径,因为它不会导致离开方法。方法中没有代码路径,因为没有代码离开方法的地方。以上是关于带有 while (true) 的特殊重载解决方案的主要内容,如果未能解决你的问题,请参考以下文章