Exp*_*ser 4 c# lifetime object-lifetime lifetime-scoping
当执行行超出代码块之外时,变量会发生什么?例如:
1 public void myMethod()
2 {
3 int number;
4 number = 5;
5 }
Run Code Online (Sandbox Code Playgroud)
因此,我们声明并设置变量。当它超出代码块(第5行)时,变量号发生了什么?
这是创建类实例的另一个示例:
7 public void myMethod()
8 {
9 Customer myClient;
10 myClient = new Customer();
11 }
Run Code Online (Sandbox Code Playgroud)
当它超出代码块(第11行)时,对象引用myClient会发生什么?
我猜这两种情况都分配了变量,但是何时将其释放呢?
作为变量,它是C#语言中的概念。在代码块外部没有任何“意外发生”,因为它在代码块内部。这个句子外面的单词单词什么都没有发生。
当然,您的意思是在代码运行时变量变成什么样的东西,但是值得记住这一区别,因为考虑到这个问题,我们将转移到变量在C#中不像在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
}
Run Code Online (Sandbox Code Playgroud)
这是编译发布时的外观:
.method public hidebysig instance void myMethod () cil managed
{
ret // Return
}
Run Code Online (Sandbox Code Playgroud)
由于未使用该值,因此编译器将其删除为无用的残差,仅编译立即返回的方法。
如果编译器未删除此类代码,则可能会出现以下情况:
.method public hidebysig instance void myMethod () cil managed
{
ldc.i4.5 // Push the number 5 onto the stack
pop // Remove value from stack
ret // Return
}
Run Code Online (Sandbox Code Playgroud)
调试版本将存储内容的时间更长,因为检查它们对于调试很有用。
当发布版本确实将内容存储在本地数组中时,它们也更有可能在方法中重用插槽。
然后将其转换为机器代码。这将类似于它的工作方式,即产生数字5,将其存储在本地(在堆栈上或在寄存器中),然后再次将其删除,或者因为不使用的变量已被删除而不执行任何操作。(也许甚至没有执行该方法;可以内联该方法,然后由于它没有执行任何操作,因此可以将其完全有效地删除)。
对于带有构造函数的类型,还有更多的事情要做:
.method public hidebysig instance void myMethod () cil managed
{
.locals init ([0] class Temp.Program/Customer) // Set-up space for a reference to a Customer
nop // Do nothing.
newobj instance void SomeNamespace/Customer::.ctor() // Call Customer constructor (results in Customer on the stack)
stloc.0 // Store the customer in the frist slot in locals
ret // Return
}
.method public hidebysig instance void myMethod () cil managed
{
newobj instance void SomeNamespace/Customer::.ctor() // Call Customer constructor (results in Customer on the stack)
pop // Remove value from stack
ret // Return
}
Run Code Online (Sandbox Code Playgroud)
在这里,两者都调用构造函数,甚至发布版本也这样做,因为它必须确保仍然发生任何副作用。
如果Customer是引用类型,还会发生更多的情况。如果它是值类型,则所有值都保存在堆栈中(尽管它可能又具有引用类型的字段)。如果是引用类型,则堆栈中保存的是对堆中对象的引用。当堆栈上不再有任何此类引用时,垃圾收集器将无法在其扫描中找到它无法收集哪些对象,然后便可以对其进行收集。
在发行版中,构造函数返回后,可能永远不会有保存该引用的内存位置或寄存器。确实,即使构造函数正在运行(如果没有字段访问或其他隐式或显式使用此操作的情况),也可能没有一个,或者它可能在此过程中被部分擦除(一旦完成此类访问),因此进行垃圾回收可能在构造函数尚未完成之前发生。
该方法返回后,它很有可能会在堆内存中徘徊一段时间,因为GC尚未运行。