前几天我在阅读这篇文章,想知道为什么在 Dispose 方法中还有一个 Finalizer。我在这里阅读了关于为什么您可能希望将 Dispose 添加到终结器的信息。我的好奇是,何时会在 Dispose 方法本身上调用终结器?是否有代码示例,或者它是否基于软件运行的系统上发生的事情?如果是这样,如果没有 GC 运行 Dispose 方法会发生什么情况。
5 回答
这里终结器的目的只是防止内存泄漏(如果您碰巧没有显式调用Dispose
)的安全预防措施。这也意味着如果您希望它们在程序关闭时释放资源,您不必处置您的对象,因为无论如何 GC 将被迫完成并收集所有对象。
与此相关的一点是,在处理对象时,与终结器略有不同,这一点很重要。
~MyClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(disposing)
{
if (!this.disposed)
{
if (disposing)
{
// Dispose managed resources here.
}
// Dispose unmanaged resources here.
}
this.disposed = true;
}
您不想在终结器中处理托管资源的原因是您实际上会在这样做时创建对它们的强引用,这可能会阻止 GC 正确完成工作并收集它们。当然,非托管资源(例如 Win32 句柄等)应始终显式关闭/处置,因为 CLR 不知道它们。
这主要是为了保护自己。你不能决定你班级的最终用户会做什么。通过在 Dispose 方法之外提供终结器,GC 将“释放”您的对象,适当地释放您的资源,即使用户忘记调用 Dispose() 或误用您的类。
当对象被垃圾回收时调用终结器。Dispose 需要显式调用。在下面的代码中,将调用终结器,但不会调用 Dispose 方法。
class Foo : IDisposable
{
public void Dispose()
{
Console.WriteLine("Disposed");
}
~Foo()
{
Console.WriteLine("Finalized");
}
}
...
public void Go()
{
Foo foo = new Foo();
}
必须显式调用 dispose 方法,方法是调用 Dispose() 或将对象放在 using 语句中。GC 将始终调用终结器,因此如果在处理对象之前需要发生某些事情,终结器至少应该检查以确保对象中的所有内容都已清理。
如果可能的话,您希望避免在终结器中清理对象,因为与事先处理它们相比,它会导致额外的工作(例如调用 dispose),但是如果周围有需要的对象,您应该始终至少检查终结器即将被删除。
一个重要但微妙的注意事项尚未提及:Dispose 的一个很少考虑的目的是防止对象被过早清理。带有终结器的对象必须仔细编写,以免终结器比预期的更早运行。终结器不能在对对象 (*) 进行的最后一个方法调用开始之前运行,但它有时可能会在如果方法完成后对象将被放弃,则最后一次方法调用。正确 Dispose 对象的代码不能在调用 Dispose 之前放弃该对象,因此终结器不会对正确使用 Dispose 的代码造成严重破坏。另一方面,如果使用对象的最后一个方法使用了在最后一次使用对象引用本身之后将在终结器中清理的实体,则垃圾收集器可能会在对象上调用 Finalize 并清理up 仍在使用的实体。补救措施是确保任何使用将被终结器清理的实体的调用方法必须在某个时候跟随使用“this”的方法调用。GC.KeepAlive(this) 是一个很好的方法。
(*) Non-virtual methods which are expanded to in-line code that doesn't do anything with the object may be exempt from this rule, but Dispose usually is, or invokes, a virtual method.