.net 垃圾收集器会调用IDisposable.Dispose吗?

.net 垃圾收集器会调用IDisposable.Dispose吗?,.net,dispose,idisposable,.net,Dispose,Idisposable,NET意味着,如果编写终结器并实现IDisposable,则终结器需要显式调用Dispose。 这是合乎逻辑的,并且是我在少数情况下经常做的事情,在这种情况下,终结器是有保证的 但是,如果我这样做会发生什么: class Foo : IDisposable { public void Dispose(){ CloseSomeHandle(); } } 不要实现终结器或任何东西。框架会为我调用Dispose方法吗 是的,我意识到这听起来很愚蠢,所有的逻辑都暗示它不会,但我总是有两件事在

NET意味着,如果编写终结器并实现IDisposable,则终结器需要显式调用Dispose。 这是合乎逻辑的,并且是我在少数情况下经常做的事情,在这种情况下,终结器是有保证的

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

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}
不要实现终结器或任何东西。框架会为我调用Dispose方法吗

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

  • 几年前有人告诉我,事实上它会这样做,而且那个人有一个非常可靠的“了解他们的东西”的记录

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


  • 虽然我已经读了很多关于它的东西,而且有很多隐含的东西,但我从来都找不到这个问题的肯定的或否定的答案。

    我不这么认为。您可以控制何时调用Dispose,这意味着您可以在理论上编写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将为您调用终结器

    不过。下一次垃圾收集,而不是被收集,对象将进入finalization que,收集所有内容,然后调用finalizer。之后的下一个集合将被释放


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

    垃圾回收器调用垃圾回收上对象的Object.Finalize方法。默认情况下,这不做任何事情,如果您想释放额外的资源,必须避免

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

    有关更多信息,请参见。不,不调用

    但这样做很容易,不要忘记处理对象。只需使用
    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将不调用dispose。它可能会调用终结器,但即使在所有情况下也不能保证这一点


    有关处理此问题的最佳方法的讨论,请参阅此部分。

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

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

    终结符不是像C++那样的确定性析构函数。正如其他人所指出的,无法保证它何时被调用,事实上,如果你有足够的内存,它是否会被调用

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

    您可能知道,也可能不知道,GC被分为几代—第0代、第1代和第2代,以及大型对象堆。Split是一个松散的术语-您可以获得一块内存,但有一些指针指示Gen 0对象的开始和结束位置

    思考的过程是,你可能会使用很多短期的对象。因此,对于GC来说,这些应该是容易和快速的,以获得-gen0对象。因此,当存在内存压力时,它所做的第一件事就是收集Gen 0

    现在,如果这不能解决足够的压力,那么它会返回并进行第1代扫描(重做第0代),如果仍然不够,它会进行第2代扫描(重做第1代和第0代)。因此,清理长寿命对象可能需要一段时间,而且成本相当高(因为在操作过程中线程可能会挂起)

    这意味着如果你这样做:

    ~MyClass() { }
    
    你的目标,不管怎样,都会活到第二代。这是因为GC在垃圾收集期间无法调用终结器。因此,必须完成的对象被移动到一个特殊的队列中,由另一个线程(终结器线程——如果杀死它,会导致各种不好的事情发生)清除。这意味着您的对象挂起的时间更长,并可能强制执行更多的垃圾收集


    所以,所有这些只是为了让大家明白,您希望尽可能使用IDisposable来清理资源,并认真尝试找到使用终结器的方法。这符合应用程序的最佳利益。

    IDisposable模式的创建主要是为了由开发人员调用,如果您有一个实现IDispose的对象,则开发人员应该在对象的上下文中使用
    关键字实现
    ,或者直接调用Dispose方法

    该模式的故障安全机制是实现调用Dispose()方法的终结器。如果不这样做,您可能会造成一些内存泄漏,例如:如果您创建了一些COM包装,并且从未调用System.Runtime.Interop.Marshall.ReleaseComObject(comObject)(将放置在Dispose方法中)

    在clr中,除了跟踪包含终结器的对象并由GC将它们存储在终结器表中并在某些cl出现时调用它们之外,自动调用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;
      }
     }
    }
    
    
    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();
     }
    }