13

我可以相信一个对象在 C# 中超出范围时会被销毁并立即调用其析构函数吗?

我认为它应该是因为许多常见的编码实践(例如事务对象)都依赖于这种行为,但我不太习惯使用垃圾收集,并且对这些语言的通常行为方式知之甚少。

谢谢。

4

6 回答 6

33

不,.Net 和因此 C# 依赖于垃圾收集内存管理。所以析构函数(在 .Net 中称为终结器)在 GC 发现可以销毁对象之前不会被调用。

另外:C# 中的大多数“常规”对象没有析构函数。如果您需要析构函数模式,您应该使用Dispose Pattern实现IDisposable 接口。在一次性对象上,您还应该确保使用 using 关键字或直接调用方法调用 Dispose 方法。

进一步(希望)澄清:确定性处置在 .Net 中很有用,例如,当您需要显式释放不受 .Net 运行时管理的资源时。此类资源的示例是文件句柄、数据库连接等。这些资源在不再需要时立即释放通常很重要。因此,我们不能等待 GC 释放它们。

为了在 .Net GC 的非确定性世界中获得确定性处置(类似于 C++ 的作用域行为),.Net 类依赖于 IDisposable 接口。借用Dispose Pattern,这里有一些例子:

首先,实例化一次性资源,然后让对象超出范围,这将由 GC 来处理对象:

1.    {
2.       var dr = new DisposableResource();
3.    }

为了解决这个问题,我们可以显式地处理对象:

1.    {
2.       var dr = new DisposableResource();
3.
4.       ...
5.
6.       dr.Dispose();
7.    }

但是如果第 2 行和第 6 行之间出现问题怎么办?不会调用 Dispose。为了进一步确保无论任何异常情况最终都会调用 Dispose,我们可以执行以下操作:

1.    var dr = new DisposableResource();
2.    try
3.    {
4.       ...
5.    }
6.    finally
7.    {
8.       dr.Dispose();
9.    }

由于经常需要这种模式,C# 包含 using 关键字来简化事情。下面的例子等价于上面的例子:

1.    using (var dr = new DisposableResource())
2.    {
3.       ...
4.    }
于 2009-09-26T09:48:52.187 回答
13

不。一个对象实际上并没有“超出范围”,而是对它的引用(即你用来访问它的变量)。

一旦不再有对给定对象的引用,该对象就可以在需要时进行垃圾回收 (GC) 每当 GC 决定它需要回收您不再引用的对象的空间时,就会调用对象终结器。

如果您的对象是一个资源(例如文件句柄、数据库连接),它应该实现 IDisposable 接口(它要求对象实现一种Dispose()方法来清理任何打开的连接等)。在这种情况下,您的最佳实践是将对象创建为using块的一部分,这样当该块完成时,您的应用程序将自动调用 objectsDispose()方法,该方法将负责关闭您的文件/数据库连接/其他.

例如


using (var conn = new DbConnection())  
{ 
   // do stuff with conn  
} // conn.Dispose() is automatically called here.  

using块只是一些语法糖,它将您与conn对象的交互包装在一个try块中,以及一个finally只调用conn.Dispose()

于 2009-09-26T09:57:37.550 回答
4

C# 中没有类似 C++ 的析构函数。(C# 中有一个不同的析构函数概念,也称为终结器,它使用与 C++ 析构函数相同的语法,但它们与销毁对象无关。它们旨在为非托管资源提供清理机制。)垃圾收集器将在不再引用对象后的某个时间清理对象。不是立即的,也没有办法保证这一点。

幸运的是,您也没有真正的理由要保证这一点。如果你需要内存,那么 GC 会回收它。如果你不这样做,为什么要关心周围是否还有一些垃圾?这不是内存泄漏:GC 仍然可以找到它并随时清理它。

于 2009-09-26T09:49:36.070 回答
4

不,这不能保证。与 Java 等语言类似,在 C# 中,垃圾收集器在需要时运行(即当堆太满时)。但是,当您的对象实现时IDisposable,i。e. 他们有一个Dispose()方法,必须调用它,然后你可以利用using关键字:

using (var foo = new DisposableObject()) {
    // do something with that
}

离开该块Dispose()时将立即调用该方式。using

注意:IDisposable在许多类型中都可以找到,最显着的是 GDI+,还有数据库连接、事务等,因此这里可能确实是正确的模式。

注意 2:在幕后,上面的块将被翻译成try/finally块:

var foo = new DisposableObject();
try
{
    // do something with that
}
finally
{
    foo.Dispose();
}

但是这种翻译是由编译器完成的,非常方便不要忘记调用Dispose().

于 2009-09-26T09:50:02.613 回答
0

我认为您不应该以这种方式依赖垃圾收集器。即使您扣除他们的操作方式,也很可能在下一个版本中他们重新实现了它。

在任何情况下,对象在您取消引用它们的那一刻都不会被垃圾收集。通常,它们会被收集,直到达到某个阈值,然后才被释放。

尤其是在 Java 程序中,当您查看任务管理器上的内存消耗时,这一点非常明显。它不断地增长,突然之间每一分钟它都会再次下降。

于 2009-09-26T09:49:13.487 回答
0

否。如果您参考 CLI 规范(关于终结器的第 8.9.6.7 页)http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf,您可以找到以下内容

CLI确保在实例变得不可访问后立即调用终结器。虽然依靠内存压力来触发终结是可以接受的,但实施者应该考虑使用额外的指标

但它不能。

于 2009-09-26T09:55:36.713 回答