C# 既然.NET有一个垃圾收集器,为什么我们需要终结器/析构函数/处置模式?

C# 既然.NET有一个垃圾收集器,为什么我们需要终结器/析构函数/处置模式?,c#,.net,memory,memory-management,garbage-collection,C#,.net,Memory,Memory Management,Garbage Collection,如果我理解正确,.net运行时将始终在我之后进行清理。因此,如果我创建新对象并停止在代码中引用它们,运行时将清理这些对象并释放它们占用的内存 既然如此,为什么有些对象需要析构函数或dispose方法呢?当它们不再被引用时,运行时不会清理它们吗?某些对象可能需要清理低级项。例如需要关闭的硬件等。需要终结器来保证将稀缺资源(如文件句柄、套接字、内核对象等)释放回系统。由于终结器总是在对象生命周期结束时运行,因此它是释放这些句柄的指定位置 Dispose模式用于提供资源的确定性破坏。由于.net运行时

如果我理解正确,.net运行时将始终在我之后进行清理。因此,如果我创建新对象并停止在代码中引用它们,运行时将清理这些对象并释放它们占用的内存


既然如此,为什么有些对象需要析构函数或dispose方法呢?当它们不再被引用时,运行时不会清理它们吗?

某些对象可能需要清理低级项。例如需要关闭的硬件等。

需要终结器来保证将稀缺资源(如文件句柄、套接字、内核对象等)释放回系统。由于终结器总是在对象生命周期结束时运行,因此它是释放这些句柄的指定位置

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.
}
在上面的示例中,文件将保持锁定状态,直到垃圾收集器调用
StreamWriter
对象上的终结器。这带来了一个问题,因为在此期间,可能会再次调用该方法来写入日志,但这一次它将失败,因为文件仍然被锁定

正确的方法是在使用完对象后处置该对象:

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析构函数不同于C,则是确定性的。因此,垃圾收集器无法清理这些资源,您必须自己清理

查看MSDN文档中的IDisposable


该示例使用非托管处理程序-IntPr.

主要用于非托管代码以及与非托管代码的交互。“纯”托管代码不应该需要终结器。另一方面,一次性垃圾只是一种方便的模式,可以在处理完后强制释放某些内容。

只有在系统没有内存压力的情况下,垃圾收集器才会运行,除非它确实需要释放一些内存。这意味着,您永远无法确定GC何时运行

现在,假设您是一个数据库连接。如果让GC在您之后清理,您可能会连接到数据库的时间比需要的时间长得多,从而导致奇怪的负载情况。在这种情况下,您希望实现IDisposable,这样用户就可以调用Dispose()或使用using()来真正确保尽快关闭连接,而不必依赖GC(GC可能会在以后运行)


通常,IDisposable是在任何使用非托管资源的类上实现的。

NET垃圾收集器知道如何在.NET运行时内处理托管对象。但是Dispose模式(IDisposable)主要用于应用程序正在使用的非托管对象

换句话说,.NET运行时不一定知道如何处理每种类型的设备或句柄(关闭网络连接、文件句柄、图形设备等),因此使用IDisposable提供了一种在类型中说“让我自己实现一些清理”的方法。看到这种实现,垃圾收集器可以调用Dispose(),并确保清除托管堆之外的内容。

在少数(极少数)情况下,当不再使用纯托管对象时,可能需要执行特定操作,我一时想不出一个例子,但多年来我已经看到了一些合法的用途。但主要原因是清理对象可能正在使用的任何非托管资源

因此,一般来说,除非您使用的是非托管资源,否则不需要使用Dispose/Finalize模式

  • 有些东西垃圾收集器在你死后无法清理
  • 即使有它可以清理的东西,你也可以帮助它更快地清理

  • 因为垃圾收集器无法收集托管环境未分配的内容。因此,任何导致内存分配的对非托管API的调用都需要以老式方式收集。

    真正的原因是.net垃圾收集不是为了收集非托管资源而设计的,因此这些资源的清理工作仍然掌握在开发人员手中。 此外,当对象超出范围时,不会自动调用对象终结器。GC在某个不确定的时间调用它们。当它们被调用时,GC不会立即运行它,它会等待下一轮调用它,从而增加清理的时间,这在对象持有稀缺的非托管资源(如文件或网络连接)时不是一件好事。 进入一次性模式,开发者可以在确定的时间(调用yourobject.Dispose()或using(…)语句时)手动释放稀缺资源。 请记住,您应该调用GC.SuppressFinalize(this);在dispose方法中,通知GC该对象是手动释放的,不应最终确定。 我建议你看看K.Cwalina和B.Abrams的框架设计指南书。它很好地解释了一次性模式


    祝你好运

    前面的答案很好,但让我在此再次强调重点。尤其是,你说