当执行行超出代码块时,变量会发生什么? 例如:
1 public void myMethod()
2 {
3 int number;
4 number = 5;
5 }
所以,我们声明并设置变量。当它超出代码块(第5行)时会发生什么变量号?
这是另一个创建类实例的例子:
7 public void myMethod()
8 {
9 Customer myClient;
10 myClient = new Customer();
11 }
当它超出代码块(第11行)时,对象引用myClient会发生什么?
我猜两种情况下都会分配变量,但是当它被解除分配时?
答案 0 :(得分:4)
作为变量,它是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 // Return
}
由于未使用该值,编译器将其删除为无用的cruft,并且只编译一个立即返回的方法。
如果编译器没有删除这样的代码,我们可能会期望类似:
.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
}
Debug构建存储的时间更长,因为检查它们对于调试很有用。
当发布版本确实在本地数组中存储内容时,它们也更有可能在方法中重用插槽。
然后将其转换为机器代码。这将类似于它将如何工作,它将产生数字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
}
这里都调用构造函数,甚至发布版本都会这样做,因为它必须确保任何副作用仍然发生。
如果Customer
是引用类型,还会发生更多事情。如果它是一个值类型,则所有它都保存在堆栈中(尽管它可能依次具有引用类型的字段)。如果它是引用类型,那么堆栈中保存的是对堆中对象的引用。当堆栈中不再有任何此类引用时,垃圾收集器将无法在扫描中找到它以查找不能收集的对象,并且可以收集它。
在发布版本中,一旦构造函数返回,可能永远不会有内存位置或寄存器保存该引用。实际上,即使构造函数正在运行(如果没有字段访问或其他隐式或显式使用发生此)也可能没有,或者它可能已经部分地擦除了(一旦这样的访问)已完成),因此垃圾收集可能在构造函数完成之前发生。
在方法返回后,它很可能会在堆内存中停留一段时间,因为GC还没有运行。
答案 1 :(得分:1)
在99%的案例中,答案是“无所谓”。唯一重要的是它不再适合你。
你不应该经常关心剩余的1%。要简化这一点并不容易在SO上得到合理的答案。我唯一可以说的就是:
请注意,这并未提及有关范围的任何内容 - C#实际上并不关心范围。范围可以帮助您编写代码,而不是帮助编译器(尽管方法和更高的范围确实有助于编译时间。)
同样,在大多数情况下,你并不关心接下来会发生什么。对此的主要例外是:
Dispose
方法来处理这个问题。您可以使用using
语句来帮助解决此问题。另外,如果你四处闲逛,请注意默认情况下,连接一个调试器会让你有点麻烦。例如,本地人将保持活着,直到他们的范围结束 - 这当然是完全合法的,并且在调试时会有所帮助。
答案 2 :(得分:1)
在第一种情况下,number
是值类型,将存储在堆栈中。一旦你离开这个方法,它就不复存在了。并且没有释放,堆栈空间将仅用于其他事情。
在第二种情况下,由于Customer
(我假设)是引用类型,myClient
将在堆栈上存储对实例的引用。离开方法后,该引用不再存在。这意味着该实例最终将被垃圾收集。
答案 3 :(得分:1)
假设你在Debug下运行,没有优化:
当它超出代码块(第5行)时会发生什么变量 号码?
方法退出后,该值将从堆栈中弹出。值类型存在于堆栈中的事实是实现细节,您不应该依赖它。如果这是一个值类型,它是class
中的一个字段,它就不会存在于堆栈上但是在堆上。
当它超出代码块(第5行)时会发生什么变量 号码?
假设Customer
是class
而不是struct
,并且没有实现终结器(这改变了事情的过程),它将不再有任何对象在行后引用它9.一旦GC开始(在任意的,非确定的时间),它将看到它有资格收集并在标记阶段将其标记为如此。一旦扫描阶段开始,它将释放占用的内存。
答案 4 :(得分:1)
当结构类型的字段引用丢失时 - 释放内存(堆栈中)。对于参考类型,它更复杂。如果对象(类)不再使用并且对它的引用丢失,那么它被垃圾收集器标记为删除。如果没有任何更改,则在下一次垃圾回收时删除此对象。
如果您不想等待GC自动运行它的方法,您可以通过调用GC.Collect()方法自行执行此操作
P.S。 在破坏类对象并释放内存之前(如果它实现了IDisposable接口),它会一个接一个地调用三个方法:
1. Dispose() 2. Finalize() 3. ~ctor()
在C#中你可以使用其中两个:dispose()和finalize()。当Finalize更适合为非托管资源释放编写逻辑时,Dispose通常用于释放托管资源(例如FileStream
或Threads
)。
更改object.Finalize()
方法的逻辑 - 将您的逻辑放入~ctor()
但要小心,因为它可能会导致一些严重的故障。