如果我理解正确,.net 运行时总是会在我之后清理。因此,如果我创建新对象并停止在代码中引用它们,运行时将清理这些对象并释放它们占用的内存。
既然是这种情况,为什么某些对象需要具有析构函数或处置方法?当它们不再被引用时,运行时不会在它们之后清理吗?
如果我理解正确,.net 运行时总是会在我之后清理。因此,如果我创建新对象并停止在代码中引用它们,运行时将清理这些对象并释放它们占用的内存。
既然是这种情况,为什么某些对象需要具有析构函数或处置方法?当它们不再被引用时,运行时不会在它们之后清理吗?
需要终结器来保证将稀缺资源释放回系统,例如文件句柄、套接字、内核对象等。由于终结器总是在对象生命周期结束时运行,因此它是释放这些句柄的指定位置。
该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# 不同,是确定性的。
以前的答案很好,但让我再次强调这里的重点。特别是,你说
如果我理解正确,.net 运行时总是会在我之后清理。
这只是部分正确。事实上,.NET只为一种特定资源提供自动管理:主内存。所有其他资源都需要手动清理。1)
奇怪的是,在几乎所有关于程序资源的讨论中,主存都获得了特殊的地位。这当然有一个很好的理由——主存储器通常是最稀缺的资源。但值得记住的是,还有其他类型的资源也需要管理。
1)通常尝试的解决方案是将其他资源的生命周期与代码中的内存位置或标识符的生命周期耦合——因此存在终结器。
垃圾收集器只有在系统没有内存压力时才会运行,除非它真的需要释放一些内存。这意味着,您永远无法确定 GC 何时运行。
现在,假设您是一个数据库连接。如果您让 GC 清理之后,您可能会连接到数据库的时间比需要的时间长得多,从而导致奇怪的负载情况。在这种情况下,您希望实现 IDisposable,以便用户可以调用 Dispose() 或使用 using() 来真正确保尽快关闭连接,而不必依赖可能稍后运行的 GC。
通常,IDisposable 在任何使用非托管资源的类上实现。
真正的原因是因为 .net 垃圾收集并非旨在收集非托管资源,因此这些资源的清理工作仍然掌握在开发者手中。此外,当对象超出范围时,不会自动调用对象终结器。GC 在某个未确定的时间调用它们。而且当它们被调用时,GC 不会立即运行它,它会等待下一轮调用它,从而增加清理更多的时间,当您的对象持有稀缺的非托管资源(例如文件)时,这不是一件好事或网络连接)。进入一次性模式,开发人员可以在确定的时间手动释放稀缺资源(调用 yourobject.Dispose() 或 using(...) 语句时)。请记住,您应该调用 GC.SuppressFinalize(this); 在您的 dispose 方法中告诉 GC 该对象是手动处置的,不应最终确定。我建议你看看 K. Cwalina 和 B. Abrams 的框架设计指南一书。它很好地解释了 Disposable 模式。
祝你好运!
简单的解释:
实现 Finalize 方法的一些准则:
实现 Dispose 方法的一些准则:
需要析构函数和处置方法的对象正在使用非托管资源。所以垃圾收集器不能清理那些资源,你必须自己做。
查看 IDisposable 的 MSDN 文档;http://msdn.microsoft.com/en-us/library/system.idisposable.aspx
该示例使用非托管处理程序 - IntPr。
一些对象可能需要清理低级项目。比如需要关闭的硬件等。
主要针对非托管代码,以及与非托管代码的交互。“纯”托管代码永远不需要终结器。另一方面,Disposable 只是一种方便的模式,可以在您完成后强制释放某些东西。
.NET 垃圾收集器知道如何在 .NET 运行时处理托管对象。但是 Dispose 模式 (IDisposable) 主要用于应用程序正在使用的非托管对象。
换句话说,.NET 运行时不一定知道如何处理每种类型的设备或在那里处理(关闭网络连接、文件句柄、图形设备等),因此使用 IDisposable 提供了一种说“让我在一个类型中实现一些我自己的清理。看到该实现,垃圾收集器可以调用 Dispose() 并确保清理托管堆之外的东西。
在少数(非常少)情况下,当不再使用纯托管对象时,可能需要执行特定操作,我想不出一个例子,但我见过几个多年来的合法用途。但主要原因是清理对象可能正在使用的任何非托管资源。
因此,一般来说,除非您使用非托管资源,否则您不需要使用 Dispose/Finalize 模式。
因为垃圾收集器无法收集托管环境未分配的内容。因此,任何导致内存分配的非托管 API 调用都需要以老式方式收集。