可变寿命

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会发生什么?

我猜这两种情况都分配了变量,但是何时将其释放呢?

Jon*_*nna 5

作为变量,它是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尚未运行。