垃圾收集器是否会为我调用IDisposable.Dispose?

Ori*_*rds 126 .net dispose idisposable

.NET IDisposable Pattern 意味着如果您编写终结器并实现IDisposable,则终结器需要显式调用Dispose.这是合乎逻辑的,而且在极少数情况下我总是会做终结器的保证.

但是,如果我这样做会发生什么:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}
Run Code Online (Sandbox Code Playgroud)

并且不要实现终结器或任何东西.框架会为我调用Dispose方法吗?

是的,我意识到这听起来很愚蠢,而且所有的逻辑都暗示它不会,但我总是有两件事让我不确定.

  1. 几年前有人曾告诉我,事实上它会这样做,而且那个人有"非常了解他们的东西"的良好记录.

  2. 编译器/框架根据您实现的接口(例如:foreach,扩展方法,基于属性的序列化等)执行其他"神奇"操作,因此这也可能是"魔术".

虽然我已经阅读了很多关于它的内容,并且有很多暗示的内容,但我从来没有能够找到这个问题的肯定是或否答案.

Xia*_*ian 112

.Net垃圾收集器在垃圾收集上调用对象的Object.Finalize方法.通过默认情况下这并没有什么,如果你想释放更多的资源必须overidden.

如果要释放资源,则不会自动调用Dispose并且必须显式调用Dispose ,例如在"using"或"try finally"块中

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

  • 实际上,如果它没有被覆盖,我根本不相信GC会调用Object.Finalize.确定该对象实际上没有终结器,并且终止被终止 - 这使得它更有效,因为该对象不需要在终结/可自由队列上. (33认同)
  • 根据MSDN:http://msdn.microsoft.com/en-us/library/system.object.finalize%28v=vs.110%29.aspx你实际上无法"覆盖"C#中的Object.Finalize方法,编译器生成错误:不要覆盖object.Finalize.相反,提供一个析构函数.; 即你必须实现一个有效充当Finalizer的析构函数.[刚刚在这里添加了完整性,因为这是已接受的答案,最有可能被阅读] (6认同)
  • GC 对未重写 Finalizer 的对象不执行任何操作。它不会被放入终结队列中 - 并且不会调用终结器。 (2认同)

Cor*_*Foy 63

我想强调Brian在评论中的观点,因为它很重要.

终结器不是像C++中那样的确定性析构函数.正如其他人所指出的那样,没有什么时候会被称为保证,而事实上,如果你有足够的内存,如果将永远被调用.

但是关于终结器的坏处是,正如Brian所说,它会使你的对象在垃圾收集中存活下来.这可能很糟糕.为什么?

正如您可能知道或不知道的那样,GC分为几代 - Gen 0,1和2,以及大对象堆.Split是一个松散的术语 - 你得到一块内存,但是有一些指向Gen 0对象的起点和终点.

思考过程是你可能会使用很多短暂的物体.因此,对于GC来说,这些应该是简单快速的 - Gen 0对象.因此,当存在内存压力时,它首先做的是Gen 0集合.

现在,如果那不能解决足够的压力,那么它会返回并进行第1代扫描(重做第0代),然后如果仍然不够,则执行第2代扫描(重做第1代和第0代).因此,清理长寿命对象可能需要一段时间并且相当昂贵(因为在操作期间您的线程可能会被挂起).

这意味着,如果您执行以下操作:

~MyClass() { }
Run Code Online (Sandbox Code Playgroud)

无论如何,您的对象都将存在于第2代.这是因为GC无法在垃圾回收期间调用终结器.所以必须最终确定的对象被移动到一个特殊的队列,由另一个线程清理掉(终结器线程 - 如果你杀了它就会发生各种坏事).这意味着您的对象会更长时间地挂起,并可能会强制更多垃圾收集.

所以,所有这一切只是为了让你想要使用IDisposable来尽可能地清理资源,并认真尝试找到使用终结器的方法.这符合您的应用程序的最佳利益.

  • 我同意你想尽可能使用IDisposable,但你也应该有一个调用dispose方法的终结器.在调用dispose方法之后,可以在IDispose.Dispose中调用GC.SuppressFinalize(),以确保您的对象不会被放入终结器队列中. (8认同)
  • 世代编号为 0-2,而不是 1-3,但您的帖子在其他方面很好。不过,我要补充一点,您的对象引用的任何对象,或这些对象引用的任何对象等,也将在另一代受到垃圾收集(尽管不是终结)的保护。因此,带有终结器的对象不应持有对终结器不需要的任何内容的引用。 (2认同)
  • 关于"你的对象,无论如何,都会活到第二代." 这是非常基本的信息!它节省了很多时间调试系统,其中有许多短期的Gen2对象"准备好"用于最终确定,但由于堆使用量很大,从未最终确定导致OutOfMemoryException.删除(甚至是空的)终结器并在其他地方移动(解决)代码,问题消失了,GC能够处理负载. (2认同)

And*_*rew 31

这里已经有很多很好的讨论了,我在派对上有点迟了,但我想自己补充几点.

  • 垃圾收集器永远不会直接为您执行Dispose方法.
  • GC 在感觉到它时执行终结器.
  • 用于具有终结器的对象的一种常见模式是让它调用一种方法,该方法按惯例定义为Dispose(bool disposing),传递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;
  }
 }
}
Run Code Online (Sandbox Code Playgroud)

这是简单的版本,但有很多细微差别可以让你在这种模式上绊倒.

  • IDisposable.Dispose的合同表明多次调用一定是安全的(在已经处置的对象上调用Dispose应该什么也不做)
  • 正确管理一次性对象的继承层次结构会变得非常复杂,特别是如果不同的层引入新的Disposable和非托管资源.在上面的模式中,Dispose(bool)是虚拟的,允许它被覆盖,以便它可以被管理,但我发现它容易出错.

在我看来,完全避免使用任何直接包含可能需要最终确定的一次性引用和本机资源的类型要好得多.SafeHandles提供了一种非常干净的方法,通过将本机资源封装到一次性内部提供自己的最终化(以及在P/Invoke中删除窗口,其中本机句柄可能由于异步异常而丢失)等许多其他好处.

简单地定义一个SafeHandle使这个简单:


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

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}
Run Code Online (Sandbox Code Playgroud)

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


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

  • 关于本例中对“GC.SuppressFinalize”的调用。在这种情况下,只有在 `Dispose(true)` 成功执行时才应调用 SuppressFinalize。如果在抑制终结之后但在清理所有资源(特别是非托管资源)之前“Dispose(true)”在某个时刻失败,那么您仍然希望发生终结,以便进行尽可能多的清理。最好在调用“Dispose(true)”之后将“GC.SuppressFinalize”调用移至“Dispose()”方法中。请参阅[框架设计指南](http://amzn.to/d2cuoN) 和[这篇文章](http://bit.ly/dhxJaJ)。 (2认同)

Mat*_*hop 6

我不这么认为.您可以控制何时调用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");
    }
}
Run Code Online (Sandbox Code Playgroud)


Bri*_*ahy 6

不是你所描述的情况,但 GC 会为你调用终结器(如果你有的话)。

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

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