作为C#语言中的一个“变量”概念,它只存在于代码块中,因此在代码块之外不会有什么“发生”。就像这句话中的单词“word”一样,在这个句子之外也不会有什么变化。
当然,你指的是当代码运行时,变量变成了什么。但记住这一区别很重要,因为考虑这个问题时,我们要转向变量在C#中不再适用的层次。
无论哪种情况,代码最终都会被编译成CIL,并在运行时编译成机器码。
CIL可能会有很大的差异。例如,下面是第一个示例在调试模式下编译的结果:
.method public hidebysig instance void myMethod () cil managed
{
.locals init ([0] int32) // Set-up space for a 32-bit value to be stored
nop // Do nothing
ldc.i4.5 // Push the number 5 onto the stack
stloc.0 // Store the number 5 in the first slot of locals
ret // Return
}
以下是编译发布后的效果:
.method public hidebysig instance void myMethod () cil managed
{
ret
}
自从值没有被使用,编译器将其作为无用的垃圾清除并编译一个立即返回的方法。
如果编译器没有删除这样的代码,我们可能会得到类似于:
.method public hidebysig instance void myMethod () cil managed
{
ldc.i4.5
pop
ret
}
调试版的保存内容时间更长,因为检查它们对于调试很有用。
当发布版本在本地变量数组中存储东西时,它们也更有可能重复使用方法内部的插槽。
然后将其转换为机器代码。这类似于它的工作方式,它会产生数字5,将其本地存储(在堆栈或寄存器中),然后再次将其丢弃,或者不执行任何操作,因为未使用的变量已被删除。(可能甚至不执行方法;该方法可以进行内联,然后由于它什么也没做而被完全删除)。
对于带构造函数的类型,有些更多的操作需要进行:
.method public hidebysig instance void myMethod () cil managed
{
.locals init ([0] class Temp.Program/Customer)
nop
newobj instance void SomeNamespace/Customer::.ctor()
stloc.0
ret
}
.method public hidebysig instance void myMethod () cil managed
{
newobj instance void SomeNamespace/Customer::.ctor()
pop
ret
}
这里都调用了构造函数,即使是发布版本也是如此,因为它必须确保任何副作用仍然发生。
如果Customer是引用类型,则会发生更多事情。如果它是值类型,则所有内容都保存在堆栈中(尽管可能有字段反过来是引用类型)。如果它是引用类型,则在堆栈中保存的是指向堆中对象的引用。当堆栈上不再有这样的引用时,垃圾收集器将无法在扫描中找到它以确定哪些对象无法收集,并且可以进行收集。
在发布版本中,一旦构造函数返回,可能永远没有内存位置或寄存器保存该引用。实际上,即使在构造函数运行时(如果没有字段访问或其他隐式或显式使用this发生),甚至可能没有一个,或者在其中间部分被擦除(一旦这些访问完成),因此在构造函数甚至还没有完成之前就可以进行垃圾回收。
更有可能的是,在方法返回后,它会在堆内存中挂起一段时间,因为垃圾回收尚未运行。