C# – 超出范围时是否会立即销毁对象?

我是否可以相信一个对象被销毁,并且当它超出C#中的范围时会立即调用它的析构函数?

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

谢谢。

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

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

进一步(希望)澄清:确定性处置在.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而不管任何exception,我们可以执行以下操作:

 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. } 

不是。 对象实际上并不“超出范围”,对它的引用(即用于访问它的变量)确实如此。

一旦没有对给定对象的更多引用,该对象就有资格在需要时进行垃圾收集(GC)。 每当GC决定它需要回收你不再引用的对象的空间时,就是在调用对象终结器时。

如果您的对象是资源(例如文件句柄,数据库连接),它应该实现IDisposable接口(这使得对象必须实现Dispose()方法来清理任何打开的连接等)。 在这种情况下,您的最佳实践是将对象创建为using块的一部分,以便在此块完成时,您的应用程序将自动调用Dispose()方法对象,该方法将负责关闭您的文件/数据库连接/什么。

例如

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

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

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

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

不,这不能保证。 与Java等语言类似,在C#中,垃圾收集器在需要时运行(即堆积过满时)。 但是,当您的对象实现IDisposable ,i。 即 他们有一个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()

我不认为你应该以这种方式依赖垃圾收集者。 即使你扣除了它们的运行方式,也很可能在下一个版本中它们已经重新实现了它。

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

特别是在java程序中,当您查看任务管理器上的内存消耗时,这是非常明显的。 它会不断增长和成长,并且每隔一分钟就会再次下降。

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

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

但一定不能。