Kyr*_*rio 69 c# conditional-compilation
考虑以下类:
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");
}
}
Run Code Online (Sandbox Code Playgroud)
现在,如果我们以这种方式调用方法:
var a = new A();
var b = a.GetB();
b.Hello();
Run Code Online (Sandbox Code Playgroud)
在发布版本中(即没有DEBUG
标志),我们只会GetB
在控制台上看到打印,因为Hello()
编译器会忽略调用.在调试版本中,两个打印都会出现.
现在让我们链接方法调用:
a.GetB().Hello();
Run Code Online (Sandbox Code Playgroud)
调试版本中的行为保持不变; 但是,如果未设置标志,我们会得到不同的结果:两个调用都被省略,并且控制台上没有打印.快速浏览一下IL可以看出整行都没有编译过.
根据C#的最新ECMA标准(ECMA-334,即C#5.0),将Conditional
属性放置在方法上时的预期行为如下(强调我的):
如果在呼叫点定义了一个或多个相关的条件编译符号,则包括对条件方法的调用,否则省略该调用.(§22.5.3)
这似乎并不表示整个链应该被忽略,因此我的问题.话虽如此,微软的C#6.0草案规范提供了更多细节:
如果定义了符号,则包括呼叫; 否则,省略呼叫(包括接收机的评估和呼叫的参数).
没有评估调用参数的事实已被充分记录,因为这是人们使用此功能而不是#if
函数体中的指令的原因之一.然而,关于"接收器的评估"的部分是新的 - 我似乎无法在其他地方找到它,它似乎解释了上述行为.
鉴于此,我的问题是:在这种情况下,C#编译器没有评估的理由 是什么a.GetB()
?根据条件调用的接收者是否存储在临时变量中,它的行为是否真的不同?
Mar*_*ell 62
它归结为这句话:
(包括接收机的评估和呼叫的参数)被省略.
在表达式中:
a.GetB().Hello();
Run Code Online (Sandbox Code Playgroud)
"接收者的评价"是:a.GetB()
.所以:根据规范省略了它,这是一个有用的技巧[Conditional]
,可以避免未使用的开销.当你把它放入本地时:
var b = a.GetB();
b.Hello();
Run Code Online (Sandbox Code Playgroud)
然后"接收器的评估"只是本地的b
,但var b = a.GetB();
仍然评估原始(即使本地b
最终被删除).
这可能会产生意想不到的后果,因此:[Conditional]
小心使用.但原因是可以轻松添加和删除日志记录和调试等内容.请注意,如果天真地对待参数也会有问题:
LogStatus("added: " + engine.DoImportantStuff());
Run Code Online (Sandbox Code Playgroud)
和:
var count = engine.DoImportantStuff();
LogStatus("added: " + count);
Run Code Online (Sandbox Code Playgroud)
如果被标记可能会有很大不同- 结果是你的实际"重要的东西"没有完成.LogStatus
[Conditional]
Eri*_*ert 19
根据条件调用的接收者是否存储在临时变量中,它的行为是否真的不同?
是.
a.GetB()
在这种情况下,C#编译器没有评估的理由是什么?
Marc和Søren的答案基本上是正确的.这个答案只是为了清楚地记录时间表.
Sør*_*æus 13
我做了一些挖掘,发现C#5.0语言规范实际上已经包含了第424.2节"条件"属性第424页中的第二个引用.
Marc Gravell的回答已经表明这种行为是有意的,在实践中意味着什么.您还询问了这背后的基本原理,但似乎对Marc提到的消除开销不满意.
也许你想知道为什么它被认为是可以删除的开销?
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();
Run Code Online (Sandbox Code Playgroud)
Hello()
在使用方法链时,只省略了调用,省略了整个链.
您还可以查看完全不同的地方以获得更好的视角:C#6.0中引入的空条件运算符或elvis运算符 ?
.虽然它只是具有空检查的更复杂表达式的语法糖,但它允许您构建类似于方法链的东西,并且可以选择基于空检查进行短路.
GetFoos()?.MakeBars()?.AnnounceBars();
如果前面的方法没有返回null
,Eg 只会到达它的末尾,否则后续的调用将被省略.
这可能是违反直觉的,但请尝试将您的场景视为与此相反:编译器会Hello()
在您的a.GetB().Hello();
链之前省略您的调用,因为您还没有到达链的末尾.
这一切都是扶手椅的推理,所以请把这个与elvis操作员的类比与一粒盐.
归档时间: |
|
查看次数: |
5370 次 |
最近记录: |