当最后一个条件调用有条件时,为什么C#编译器会删除方法调用链?

Kyrio:

考虑以下类别:

public class A {
    public B GetB() {
        Console.WriteLine("GetB");
        return new B();
    }
}

public class B {
    [System.Diagnostics.Conditional("DEBUG")]
    public void Hello() {
        Console.WriteLine("Hello");
    }
}

现在,如果我们以这种方式调用方法:

var a = new A();
var b = a.GetB();
b.Hello();

在发布版本中(即,无DEBUG标志),我们只会看到GetB控制台上的打印内容,因为Hello()编译器将省略对的调用在调试版本中,两个打印都会出现。

现在让我们链接方法调用:

a.GetB().Hello();

调试版本中的行为不变。但是,如果未设置该标志,则会得到不同的结果:两个调用都被省略,并且控制台上没有打印内容。快速浏览一下IL会发现整个行没有被编译。

根据针对C#最新ECMA标准(ECMA-334,即C#5.0),将Conditional属性置于方法上时的预期行为如下(强调我的意思):

如果在调用点定义了一个或多个与其相关的条件编译符号,则包括对条件方法的调用,否则将省略该调用(第22.5.3节)

这似乎并不表示应忽略整个链条,因此是我的问题。话虽如此,MicrosoftC#6.0规范草案提供了更多细节:

如果定义了该符号,则包括该呼叫;否则,将忽略该呼叫(包括对接收方的评估和该呼叫的参数)。

没有对调用的参数进行评估的事实有据可查,因为这是人们使用此功能而不是#if函数主体中的指令的原因之一但是,有关“接收者评估”的部分是新的-我似乎无法在其他地方找到它,并且确实可以解释上述行为。

有鉴于此,我的问题是:在这种情况下,C#编译器不进行评估的原理 什么a.GetB() 根据条件调用的接收者是否存储在临时变量中,它的行为是否真的有所不同?

SørenD.Ptæus:

我做了一些挖掘,发现C#5.0语言规范实际上确实已经在第424页上的17.4.2的Conditional属性中包含您的第二个引号

马克·格雷韦尔(Marc Gravell)的答案已经表明,这种行为是有意的,在实践中是什么意思。您还询问了理由背后却似乎被去除开销马克的被提及的不满。

也许您想知道为什么将其视为可以消除的开销?

a.GetB().Hello();在您的场景中根本没有被调用而Hello()被忽略,从表面上看可能很奇怪。

我不知道该决定背后的理由,但我发现了一些合理的推理依据。也许它也可以为您提供帮助。

仅当每个先前的方法都有返回值时,才可以进行方法链接当您想对这些值做某事时,这很有意义,即a.GetFoos().MakeBars().AnnounceBars();

如果您有一个只执行某些操作而没有返回值的函数,您不能在其后面链接某些东西,而可以将其放在方法链的末尾,就像您的条件方法一样,因为它必须具有返回类型void。

还要注意,前面的方法调用结果丢弃了,因此在您的示例中,执行此语句后a.GetB().Hello();的结果GetB()没有理由继续存在。基本上,您暗示GetB()只需要使用的结果Hello()

如果Hello()省略了您为什么要GetB()呢?如果您忽略Hello()了行,则将其归结为a.GetB();没有任何分配,并且许多工具会警告您您没有使用返回值,因为这很少是您想要执行的操作。

您似乎对此方法不满意的原因不仅是尝试执行返回某个值所需的操作,而且还产生副作用,即I / O。如果您确实有一个纯函数,那么实际上就没有理由GetB()忽略后续调用,即,如果您不打算对结果做任何事情。

如果将的结果分配给GetB()变量,则它本身就是一条语句,并且无论如何都会执行。所以这种推理解释了为什么

var b = a.GetB();
b.Hello();

Hello()当使用方法链接时仅省略对的调用,而整个链被忽略。

您也可以在完全不同的地方查看以获得更好的视角:C#6.0中引入空条件运算符elvis运算符 ?尽管对于带有null检查的更复杂的表达式来说,它只是语法糖,但它允许您构建类似于方法链之类的方法,并可以选择基于null检查的短路方式。

例如,GetFoos()?.MakeBars()?.AnnounceBars();如果先前的方法不返回null,则它将到达结尾,否则将省略后续的调用。

这可能是反直觉的,但试试你的情况想象成这个倒数:编译器之前忽略你的电话Hello()在你的a.GetB().Hello();链,因为你没有达到链的末端反正。


免责声明

所有这些都是扶手椅式的推理,因此,请与猫王操作员进行一番比较,然后撒一粒盐。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

SonarQube:调用方法(一个或多个)是有条件

声纳违规:调用方法(一个或多个)是有条件

有条件删除在编译时的Java方法

有条件的调用链接方法

有条件地在另一个诺言中调用诺言

有条件地从编译器隐藏代码

有条件的constexpr类成员函数内部的非constexpr变量成员调用编译-为什么?

在另一个脚本中有条件地调用一个函数并使用它的变量

react JS中有条件调用一个函数,每次点击按钮一个函数

即使似乎满足所有条件,JIT编译器也不会内联方法

如何在bazel中有条件地指定C编译器定义?

有条件地从数字字符串中删除最后一个字段

使用LINQ方法Any()时,为什么C#编译器会创建私有DisplayClass,如何避免呢?

为什么C#编译器在静态方法调用实例方法的地方不会出错?

为什么在静态调用时编译器会接受无效的语法调用(<?>方法)?

为什么C#编译器将il代码留给条件方法?

如何有条件地调用spring验证器

有条件地从另一个组件导入一个组件的最佳方法是什么?

打字稿:具有条件返回类型的函数调用另一个这样的函数

RxJava-在第一个有条件的情况下进行两次调用

一次仅有条件地编译一个模块

如何在PostgreSQL中获取有条件的最后一个值?

获取带有条件的实体字段的最后一个值

SQL 查询以选择带有条件的最后一个条目

有条件编译时出错

SonarQube:“仅有条件地调用方法”

有条件地调用Linq扩展方法

有条件的构造函数调用

有条件的ajax调用