由于.NET有一个垃圾收集器,为什么我们需要finalizer / destructors / dispose-pattern?
如果我理解正确的话.net运行库将永远在我之后清理。 所以,如果我创build新的对象,并停止在我的代码中引用它们,运行时将清理这些对象并释放它们占用的内存。
既然是这样,那么为什么一些对象需要一个析构函数或者处理方法呢? 当他们不被引用之后,运行时不会清理它们吗?
需要终结器来保证将稀有资源释放回系统,如文件句柄,套接字,内核对象等等。由于终结器总是运行在对象生命的末尾,所以它是释放这些句柄的指定位置。
Dispose
模式用于提供确定性的资源破坏。 由于.net运行时垃圾收集器是非确定性的(这意味着您不能确定运行时何时会收集旧对象并调用它们的终结器),所以需要一种方法来确保系统资源的确定性释放。 因此,当您正确实现Dispose
模式时,您会提供确定性的资源释放,并且在消费者不小心且不处理对象的情况下,终结器将清理该对象。
为什么Dispose
需要一个简单的例子可能是一个快速和脏的日志方法:
public void Log(string line) { var sw = new StreamWriter(File.Open( "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)); sw.WriteLine(line); // Since we don't close the stream the FileStream finalizer will do that for // us but we don't know when that will be and until then the file is locked. }
在上面的示例中,文件将保持locking状态,直到垃圾回收器调用StreamWriter
对象上的终结器。 这就产生了一个问题,因为在此期间,这个方法可能会被再次调用来写一个日志,但是这次它会失败,因为这个文件仍然被locking。
正确的方法是使用它完成对象的处理:
public void Log(string line) { using (var sw = new StreamWriter(File.Open( "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))) { sw.WriteLine(line); } // Since we use the using block (which conveniently calls Dispose() for us) // the file well be closed at this point. }
顺便说一下,技术上的终结器和析构器意味着同样的事情; 我更喜欢调用c#析构函数的终结器,否则它们会倾向于将人们与C ++析构函数混淆,这与C#不同,它们是确定性的。
以前的答案是好的,但是让我再次强调一下重要的一点。 特别是你说的
如果我理解正确的话.net运行库将永远在我之后清理。
这只是部分正确的。 实际上, .NET 只为一个特定资源提供自动pipe理 :主存。 所有其他资源需要手动清理。 1)
奇怪的是,主要记忆在几乎所有有关节目资源的讨论中都具有特殊的地位。 这当然有一个很好的理由 – 主要记忆往往是最稀缺的资源。 但值得记住的是,还有其他types的资源,也需要pipe理。
1)通常尝试的解决scheme是将其他资源的生命周期与代码中的存储器位置或标识符的生命周期相结合 – 因此存在终结器。
垃圾收集器只有在系统没有受到内存压力时才会运行,除非真的需要释放一些内存。 这意味着,你永远不能确定什么时候GC会运行。
现在,想象一下你是一个数据库连接。 如果您之后让GC清理干净,则可能会连接到数据库的时间比需要的时间长得多,从而造成奇怪的负载情况。 在这种情况下,您需要实现IDisposable,以便用户可以调用Dispose()或使用()来确保连接尽快closures,而不必依赖可能会在稍后运行的GC。
通常,IDisposable在任何使用非托pipe资源的类上实现。
- 有垃圾收集器无法清理后的东西
- 即使有东西可以清理,你也可以尽快清理
真正的原因是因为.net垃圾收集不是为了收集非托pipe资源而devise的,因此这些资源的清理仍然掌握在开发者手中。 而且,当对象超出范围时,对象终结器不会自动调用。 他们在一些不确定的时间被GC呼叫。 当它们被调用时,GC不会马上运行它,它会等待下一轮调用它,增加清理时间,当你的对象持有稀less的非托pipe资源(比如文件或networking连接)。 input一次性模式,开发人员可以在确定的时间(在调用yourobject.Dispose()或using(…)语句)时手动释放稀缺资源。 请记住,您应该调用GC.SuppressFinalize(this); 在你的dispose方法中告诉GC该对象是手动处理的,不应该最终确定。 我build议你看一下由K. Cwalina和B. Abrams撰写的框架devise指南。 它解释了非常好的一次性模式。
祝你好运!
简单的解释:
- Dispose是为了确定性地处理非记忆资源,特别是稀缺资源而devise的 。 例如,窗口句柄或数据库连接。
- Finalize是为非确定性处理非内存资源而devise的,如果Dispose没有被调用,通常作为一个backstop。
一些实现Finalize方法的指导原则:
- 只对需要最终确定的对象实现Finalize,因为Finalize方法存在性能成本。
- 如果您需要Finalize方法,请考虑实现IDisposable以允许您的types的用户避免调用Finalize方法的成本。
- 你的Finalize方法应该被保护,而不是公开的。
- 你的Finalize方法应该释放这个types拥有的所有外部资源,但是只能拥有它拥有的所有外部资源。 它不应该引用任何其他资源。
- CLR不保证Finalize方法的调用顺序。 正如Daniel在他的评论中指出的那样,这意味着如果可能的话,Finalize方法不应该访问任何成员引用types,因为这些方法可能(或者可能有一天)会有自己的finalizer。
- 永远不要在types的基types以外的任何types上直接调用Finalize方法。
- 尽量避免Finalize方法中的任何未处理的exception,因为这将终止您的进程(2.0或更高版本)。
- 避免在Finalizer方法中执行任何长时间运行的任务,因为这会阻塞Finalizer线程并阻止其他Finalizer方法的执行。
一些实施Dispose方法的指导原则:
- 在封装明确需要释放的资源的types上实现configurationdevise模式。
- 在具有一个或多个保留资源的派生types的基本types上实现disposedevise模式,即使基types不包含。
- 在实例上调用Dispose之后,通过调用GC.SuppressFinalize方法来阻止运行Finalize方法。 这个规则的唯一例外情况是在Finalize中必须完成的工作,而Dispose没有涉及这种情况。
- 不要认为Dispose会被调用。 如果Dispose没有被调用,那么一个types拥有的非托pipe资源也应该在Finalize方法中被释放。
- 当已经处理资源时,抛出此types的实例方法(Dispose除外)中的ObjectDisposedException。 此规则不适用于Dispose方法,因为它应该可多次调用而不引发exception。
- 将调用传播到Dispose通过基types的层次结构。 Dispose方法应该释放此对象和此对象拥有的任何对象所拥有的所有资源。
- 在调用Dispose方法之后,您应该考虑不允许使用对象。 重新创build已经处理的对象是一种难以实现的模式。
- 允许Dispose方法被多次调用而不会抛出exception。 该方法在第一次通话后不应该做任何事情。
需要descructors和处置方法的对象正在使用未pipe理的资源。 所以垃圾收集器不能清理这些资源,你必须自己做。
查看IDisposable的MSDN文档; http://msdn.microsoft.com/en-us/library/system.idisposable.aspx
这个例子使用一个未被pipe理的处理程序 – IntPr。
有些对象可能需要清理低级项目。 如需要closures的硬件等
主要用于非托pipe代码,以及与非托pipe代码的交互。 “纯”托pipe代码不应该需要终结器。 另一方面,一次性使用只是一个方便的模式,当你完成它的时候,强迫某些东西被释放。
有几个(很less)的情况下,当一个纯粹的pipe理对象不再使用时,可能需要执行一个特定的操作,我不能拿出一个例子,但我已经看到一对夫妇多年来合法使用 但主要原因是清理对象可能使用的任何非托pipe资源。
所以,一般情况下,除非使用非托pipe资源,否则不需要使用Dispose / Finalize模式。
因为垃圾收集器无法收集pipe理环境未分配的内容。 因此,任何对导致内存分配的非托pipeAPI的调用都需要以旧式的方式进行收集。
.NET垃圾回收器知道如何处理.NET运行时中的托pipe对象。 但Dispose模式(IDisposable)主要用于应用程序正在使用的未pipe理对象。
换句话说,.NET运行时不一定知道如何处理每种types的设备或处理那些设备(closuresnetworking连接,文件句柄,graphics设备等),所以使用IDisposable提供了一种方法来说“让我实施一些自己的清理“。 看到这个实现,垃圾收集器可以调用Dispose()并确保托pipe堆以外的东西被清理。