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%。要将这一点简化到足以给出合理的答案是不容易的。我唯一能简单地说的是:

  • 一旦将来不再使用变量,编译器或运行时就可以随心所欲地执行以下操作:)
请注意,这并没有提到任何有关范围的内容—C实际上并不太关心范围。作用域用于帮助您编写代码,而不是帮助编译器(尽管方法和更高的作用域肯定有助于编译时间)

同样,在大多数情况下,你不在乎接下来会发生什么。这方面的主要例外是:

  • 使用非托管资源。确定地处置非托管资源通常是个好主意。所有封装非托管资源的类都有一个
    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
}