C# 可变寿命
当执行行超出代码块时,变量会发生什么变化? 例如:C# 可变寿命,c#,lifetime,object-lifetime,lifetime-scoping,C#,Lifetime,Object Lifetime,Lifetime Scoping,当执行行超出代码块时,变量会发生什么变化? 例如: 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
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会发生什么
我猜在这两种情况下都分配了变量,但当它被释放时?在99%的情况下,答案是“没关系”。唯一重要的是你再也找不到它了 你不应该太在意剩下的1%。要将这一点简化到足以给出合理的答案是不容易的。我唯一能简单地说的是:
- 一旦将来不再使用变量,编译器或运行时就可以随心所欲地执行以下操作:)
- 使用非托管资源。确定地处置非托管资源通常是个好主意。所有封装非托管资源的类都有一个
方法来处理此问题。您可以使用Dispose
语句来帮助执行此操作using
- 性能瓶颈-如果分析显示您在不切实际的释放过程中正在丢失内存/CPU,您可能需要一些帮助
- 将对对象的引用保留在范围之外。很容易意外地阻止收集不再使用但仍有引用的内容。这是托管应用程序内存泄漏的主要原因
此外,如果您开始尝试使用这个工具,请注意,默认情况下,附加调试器会给您带来一些麻烦。例如,局部变量将一直保持活动状态,直到其作用域结束-这当然是完全合法的,并且在调试时会有所帮助。在第一种情况下,
number
是一种值类型,将存储在堆栈上。一旦你离开这个方法,它就不再存在了。而且没有释放,堆栈空间将被简单地用于其他事情
在第二种情况下,由于
Customer
(我假设)是引用类型,myClient
将在堆栈上存储对实例的引用。一旦离开该方法,该引用就不再存在。这意味着该实例最终将被垃圾收集。假设您在调试下运行,并且没有对以下各项进行优化:
当它超出代码块(第5行)时,变量发生了什么
号码
一旦方法退出,该值就会从堆栈中弹出。值类型存在于堆栈上这一事实是一个实现细节,您不应该依赖于此。如果这是一个值类型,它是类
中的一个字段,那么它将不在堆栈上,而是在堆上
当它超出代码块(第5行)时,变量发生了什么
号码
假设
Customer
是一个类
而不是一个结构
,并且没有实现终结器(这会改变事情的进程),那么在第9行之后,它将不再有任何对象引用它。一旦GC启动(在任意的、不确定的时间),它将认为它符合收集条件,并在标记阶段将其标记为符合收集条件。一旦扫描阶段开始,它将释放占用的内存。当对结构类型字段的引用丢失时-内存被释放(在堆栈中)。对于引用类型,它更复杂。若对象(类)不再使用并且对它的引用丢失,那个么垃圾收集器会将其标记为删除。如果没有任何更改,则在下次垃圾回收时删除此对象
如果您不想等待GC自动运行它的方法,您可以通过调用GC.Collect()方法自己完成
附言。
在销毁类对象和释放内存(如果它实现IDisposable接口)之前,它会依次调用三个方法:
1. Dispose() 2. Finalize() 3. ~ctor()
在C#中,可以使用其中两种:dispose()和finalize()。Dispose通常用于释放托管资源(例如,FileStream
或Threads
),因为Finalize更适合为非托管资源释放编写逻辑
要更改object.Finalize()
方法的逻辑,请将逻辑放入~ctor()中
但是要小心,因为它可能会导致一些严重的故障。作为一个变量,它是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
}
下面是编译发布时的外观:
.method public hidebysig instance void myMethod () cil managed
{
ret // Return
}
由于没有使用该值,编译器将其作为无用的积垢删除,并编译一个立即返回的方法
如果编译器没有删除这样的代码,我们可能会看到如下情况:
.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
}
调试构建会存储更长的时间,因为检查它们对调试很有用
当发布版本确实在局部数组中存储内容时,它们也更有可能在方法中重用插槽
然后将其转换为机器代码。这在方式上是类似的
.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
}