C#/JIT 编译器可能会删除包含将变量赋值到属性的行,以后不会使用该变量
本文关键字:变量 属性 赋值 编译器 JIT 删除 包含 | 更新日期: 2023-09-27 18:31:40
我有以下代码:
var ad = product.AttributesDictionary;
var ddl = product.DetailedDescriptionList;
var rdl = product.RelatedProductList;
我已经读到C#/JIT编译器可以做很多优化。由于赋值后不使用变量。我担心 C#/JIT 编译器可能会省略这些行以进行优化。
请不要告诉我删除此行。
更新:我认为没有人能够理解我的意思。我的问题是,如果您将属性分配给变量然后不使用此变量,C#/JIT 编译器是否会省略此代码?
(这不是特定于C#编译器的,这只是一个一般的观察)
将属性分配给变量实际上包含两个部分:
- 执行属性的 get 方法
- 将方法的返回值分配给变量。
由于第一部分可以像任何方法一样复杂,并且有很多副作用,因此没有编译器会对其进行优化。
如果在赋值后未使用该变量,则可以优化第二部分,如果编译器正在积极优化。
但是,还有另一种优化器的行为不太可预测 - 下一个看到该代码的开发人员。
这些行看起来什么都不做,所以如果下一个看到它们的人是一个讨厌多余代码的学究(就像大多数开发人员一样),这些行可以删除而无需再看一眼。
尝试找到一种更好 - 更明确 - 的方式来完成您试图用这些线条完成的任何事情;
如果您尝试使用虚拟调用强制初始化蝇量级缓存,那么您可以尝试ToString()
,就像我相信编译器不会省略该行一样product.AttributesDictionary.ToString();
您可能需要检查product.AttributesDictionary
是否为 null,具体取决于缓存的行为。
好吧,让我们看看规范对此有何说明:
第 3.10 节:执行顺序
C# 程序的执行继续进行,使得每个程序的副作用 执行线程保留在关键执行点。A面 效果定义为对易失性字段的读取或写入,对 非易失性变量、对外部资源的写入以及 引发异常。
[..]
此外,执行环境不需要计算 表达式,如果它可以推断出该表达式的值未被使用 并且不会产生所需的副作用(包括任何由 调用方法或访问易失性字段)。
由于您的代码显然可能会引发异常,因此如果优化了访问,这将是一个编译器错误。
因此,此代码在技术上是安全的。从访问者那里抛出例外不仅符合法律条文,而且符合精神:访问者是方法,方法当然可以预期抛出,这取决于具体情况。
但是,在此代码面向人类的一面,事情看起来并不好。如果有合理的期望,你想要检查某些内容,你的代码应该启用它:
obj.TrySomething()
它不应该要求您这样做:
try { obj.DoSomething(); }
catch (SomeException e) { ... }
可以通过将此代码编译到程序集(DLL 或 EXE)中,然后使用 ILDASM 将其转换为纯文本 IL 来找到答案。
这仍然不会确切地告诉您运行时会发生什么,因为 JIT 将 IL 转换为 x86/x64 机器代码。但是Visual Studio调试器会向你显示反汇编,允许你进一步挖掘。
我已经尝试过了,使用以下代码:
public class Example
{
public string SomeProperty { get; set; }
public static void DoSomething(Example instance)
{
var value = instance.SomeProperty;
}
}
该方法DoSomething
编译为以下 IL(由 ILSpy 提供):
.method public hidebysig static
void DoSomething (
class Example 'instance'
) cil managed
{
// Method begins at RVA 0x2061
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: callvirt instance string Example::get_SomeProperty()
IL_0006: pop
IL_0007: ret
} // end of method Example::DoSomething
您将看到属性访问器get_SomeProperty()
被调用,即使在可以编写的最基本的属性访问器的情况下,并且当类型在同一程序集中定义时也是如此。