垃圾收集者会打电话给我吗?

那个。NET 一次性模式 暗示,如果编写终结器并实现 IDisposable,则终结器需要显式调用 Dispose。 这是合乎逻辑的,在极少数需要终结器的情况下,我总是这样做。

然而,如果我这样做会发生什么:

class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}

并且不要实现终结器或者其他东西。框架会为我调用 Dispose 方法吗?

是的,我知道这听起来很愚蠢,而且所有的逻辑都暗示这不会发生,但是我总是有两件事情在我的脑后让我不确定。

  1. 几年前有人曾经告诉我,它实际上会做到这一点,而且那个人有一个非常坚实的记录“知道他们的东西。”

  2. 编译器/框架根据您实现的接口(例如: foreach、扩展方法、基于属性的序列化等)做其他“神奇”的事情,所以这也可能是“神奇”的。

虽然我读了很多关于它的东西,有很多东西暗示,我从来没有能够找到一个 很明确是或否回答这个问题。

35969 次浏览

我不这么认为。您可以控制调用 Dispose 的时间,这意味着您可以在理论上编写处置代码来假设(例如)其他对象的存在。您无法控制调用终结器的时间,因此让终结器自动调用 Dispose 是不可能的。


编辑: 我离开去测试,只是为了确认:

class Program
{
static void Main(string[] args)
{
Fred f = new Fred();
f = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Fred's gone, and he's not coming back...");
Console.ReadLine();
}
}


class Fred : IDisposable
{
~Fred()
{
Console.WriteLine("Being finalized");
}


void IDisposable.Dispose()
{
Console.WriteLine("Being Disposed");
}
}

不是你描述的那种情况, 但是 GC 会为你调用 终结者,如果你有的话。

但是。下一个垃圾收集,对象不会被收集,而是进入终结操作 que,所有内容都被收集,然后它的终结器被调用。之后的下一个集合将被释放。

根据应用程序的内存压力,可能有一段时间没有生成对象的 gc。因此,在文件流或数据库连接的情况下,您可能需要等待一段时间才能在终结器调用中释放非托管资源,这会导致一些问题。

那个。Net 垃圾收集器调用对象。垃圾回收时对象的 Finalize 方法。通过 违约这做 没什么,必须被覆盖,如果你想释放更多的资源。

Dispose 不会自动调用,如果要释放资源,例如在“ using”或“ try finally”块中,则必须调用 清晰明了

有关详细信息,请参阅 http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx

不,不是这么叫的。

但是这很容易让你忘记处理你的对象。只要使用 using关键字。

我为此做了以下测试:

class Program
{
static void Main(string[] args)
{
Foo foo = new Foo();
foo = null;
Console.WriteLine("foo is null");
GC.Collect();
Console.WriteLine("GC Called");
Console.ReadLine();
}
}


class Foo : IDisposable
{
public void Dispose()
{


Console.WriteLine("Disposed!");
}

GC 将 没有调用释放。它 调用您的终结器,但是在所有情况下都不能保证这一点。

有关处理此问题的最佳方法的讨论,请参阅此 文章

关于 一次性手机的文档给出了一个非常清晰和详细的行为解释,以及示例代码。GC 不会调用接口上的 Dispose()方法,但会调用对象的终结器。

我想在布莱恩的评论中强调他的观点,因为这很重要。

终结器与 C + + 中的确定性析构函数不同。正如其他人指出的,不能保证什么时候调用它,实际上,如果您有足够的内存,如果它将 永远不会被调用。

但是终结器的坏处是,正如 Brian 所说,它会导致对象在垃圾收集中存活下来。情况可能很糟。为什么?

您可能知道,也可能不知道,GC 分为几代——第0代、第1代和第2代,再加上大型对象堆。拆分是一个松散的术语-你得到一块内存,但有指针的第0代对象开始和结束。

思考的过程是你可能会使用很多短命的对象。所以这些对于 GC 来说应该很容易很快就能到达-Gen 0对象。因此,当存在内存压力时,它首先要做的就是收集第0代内存。

现在,如果这还不能解决足够的压力,那么它就回去做第一代清扫(重做第0代) ,如果仍然不够,它就做第2代清扫(重做第1代和第0代)。因此,清除长期存在的对象可能需要一段时间,而且代价相当高(因为您的线程可能在操作期间暂停)。

这意味着如果你这样做:

~MyClass() { }

您的对象,无论如何,将生活到第二代。这是因为 GC 无法在垃圾收集期间调用终结器。因此,必须最终完成的对象被移动到一个特殊的队列中,由另一个线程(终结器线程——如果您终结了它,就会发生各种糟糕的事情)清除。这意味着对象挂起的时间更长,并可能强制执行更多的垃圾收集。

所以,所有这些只是为了说明一点,即您希望尽可能使用 IDisposable 清理资源,并认真地尝试找到使用终结器的方法。这对你的申请最有利。

IDisposable 模式主要由开发人员调用,如果您有一个实现 IDispose 的对象,那么开发人员应该围绕该对象的上下文实现 using关键字,或者直接调用 Dispose 方法。

该模式的故障安全措施是实现调用 Dispose ()方法的终结器。如果你不这样做,你可能会造成一些内存泄漏,例如: 如果你创建了一些 COM 包装器,从来没有调用系统。运行时间。互联系统。马歇尔。ReleaseComObject (comObject)(将放置在 Dispose 方法中)。

Clr 中没有自动调用 Dispose 方法的魔法,除了跟踪包含终结器的对象并通过 GC 将它们存储在 Finalizer 表中,并在 GC 启动一些清理试探程序时调用它们。

这里已经有很多很好的讨论了,我有点迟到了,但是我想自己加几点。

  • 垃圾收集器永远不会直接为您执行 Dispose 方法。
  • GC 威尔根据需要执行终结器。
  • 对于拥有终结器的对象,一种常见的模式是让它调用一个方法,这个方法按照惯例被定义为 Dispose (bool Dispose) ,传递 false 来表示调用是由于终结而不是显式的 Dispose 调用而发生的。
  • 这是因为在终结对象时对其他托管对象进行任何假设都是不安全的(它们可能已经被终结)。

class SomeObject : IDisposable {
IntPtr _SomeNativeHandle;
FileStream _SomeFileStream;


// Something useful here


~ SomeObject() {
Dispose(false);
}


public void Dispose() {
Dispose(true);
}


protected virtual void Dispose(bool disposing) {
if(disposing) {
GC.SuppressFinalize(this);
//Because the object was explicitly disposed, there will be no need to
//run the finalizer.  Suppressing it reduces pressure on the GC


//The managed reference to an IDisposable is disposed only if the
_SomeFileStream.Dispose();
}


//Regardless, clean up the native handle ourselves.  Because it is simple a member
// of the current instance, the GC can't have done anything to it,
// and this is the onlyplace to safely clean up


if(IntPtr.Zero != _SomeNativeHandle) {
NativeMethods.CloseHandle(_SomeNativeHandle);
_SomeNativeHandle = IntPtr.Zero;
}
}
}

这是一个简单的版本,但是有很多细微的差别会让你在这个模式上出错。

  • 一次性手机合同。Dispose 指示多次调用是安全的(对已经释放的对象调用 Dispose 不应该执行任何操作)
  • 正确管理一次性对象的继承层次结构会变得非常复杂,特别是当不同层引入新的一次性和非托管资源时。在上述模式中,Dispose (bool)是虚拟的,以允许重写它,从而可以对其进行管理,但是我发现它很容易出错。

在我看来,最好完全避免使用任何直接包含可抛弃引用和可能需要终止的本机资源的类型。SafeHandles 提供了一种非常简洁的方式来实现这一点,它将本地资源封装为一次性资源,这些资源在内部提供自己的终结(还有许多其他好处,比如在 P/Invoke 期间移除窗口,在 P/Invoke 期间,由于异步异常,本地句柄可能会丢失)。

简单地定义一个 SafeHandle 使得这个问题变得微不足道:


private class SomeSafeHandle
: SafeHandleZeroOrMinusOneIsInvalid {
public SomeSafeHandle()
: base(true)
{ }


protected override bool ReleaseHandle()
{ return NativeMethods.CloseHandle(handle); }
}

允许您将包含类型简化为:


class SomeObject : IDisposable {
SomeSafeHandle _SomeSafeHandle;
FileStream _SomeFileStream;
// Something useful here
public virtual void Dispose() {
_SomeSafeHandle.Dispose();
_SomeFileStream.Dispose();
}
}