垃圾收集器会打给IDisposable.Dispose给我吗?
.NET IDisposable模式意味着,如果编写终结器并实现IDisposable,则终结器需要显式调用Dispose。
这是合乎逻辑的,这是我在需要终结器的极少数情况下经常执行的操作。
但是,如果我这样做,会发生什么:
class Foo : IDisposable { public void Dispose(){ CloseSomeHandle(); } }
并且不要实现终结器或者其他任何东西。框架会为我调用Dispose方法吗?
是的,我意识到这听起来很愚蠢,所有逻辑都暗示这不会,但是我总是有两件事让我不确定。
- 几年前有人告诉我,实际上它会这样做,而且那个人在"知道自己的东西"方面有着非常可靠的记录。
- 编译器/框架根据我们实现的接口(例如:foreach,扩展方法,基于属性的序列化等)来执行其他"魔术"操作,因此这也可能是"魔术"的。
尽管我已经阅读了很多有关此书的内容,并且暗示了很多内容,但我始终无法找到该问题的明确答案"是"或者"否"。
解决方案
回答
我不这么认为。我们可以控制何时调用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,GC将为我们调用终结器。
然而。下一个垃圾回收而不是被回收,对象将进入终结队列,所有内容都被收集,然后将其称为终结器。之后的下一个集合将被释放。
根据我们应用程序的内存压力,我们可能会暂时没有gc来生成该对象。因此,就文件流或者db连接而言,我们可能需要等待一会儿,以便在finalizer调用中释放非托管资源一会儿,从而导致一些问题。
回答
.Net垃圾收集器在垃圾收集上调用对象的Object.Finalize方法。默认情况下,此操作不执行任何操作,如果要释放其他资源,则必须将其忽略。
如果要释放资源(例如在"使用"或者"最终尝试"块中),则不会自动调用Dispose,而必须显式调用Dispose。
有关更多信息,请参见http://msdn.microsoft.com/zh-cn/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将不会调用处置。它可能会调用终结器,但即使在所有情况下也无法保证。
有关解决此问题的最佳方法的讨论,请参见本文。
回答
有关IDisposable的文档对行为以及示例代码进行了非常清晰,详细的说明。 GC不会在接口上调用Dispose()方法,但是会为对象调用终结器。
回答
我想在他的评论中强调布莱恩的观点,因为它很重要。
终结器不是像C ++中那样的确定性析构函数。就像其他人指出的那样,不能保证何时调用它,实际上不能保证是否有足够的内存可以调用。
但是,关于终结器的坏处在于,正如Brian所说,它会使对象幸免于垃圾回收。这可能是不好的。为什么?
我们可能知道也可能不知道,GC分为第0代,第1代和第2代,以及大对象堆。拆分是一个宽松的术语,我们只能获得一个内存块,但是其中存在第0代对象开始和结束位置的指针。
思考过程是,我们可能会使用很多寿命短的对象。因此,对于GC到达Gen 0对象而言,这些操作应该简单快捷。因此,当存在内存压力时,它要做的第一件事就是Gen 0集合。
现在,如果那不能解决足够的压力,则返回并执行Gen 1扫描(重做Gen 0),然后如果仍然不够,则执行Gen 2扫描(重做Gen 1和Gen 0)。因此清理长寿命的对象可能要花一些时间,而且相当昂贵(因为在操作过程中可能会挂起线程)。
这意味着,如果我们执行以下操作:
~MyClass() { }
无论如何,对象都将保留到第2代。这是因为GC无法在垃圾回收期间调用终结器。因此,必须终结的对象被移到一个特殊的队列,以由另一个线程清除(终结器线程,如果我们将其杀死,则将导致各种不良情况发生)。这意味着对象会停留更长的时间,并可能导致更多的垃圾回收。
因此,所有这一切只是为了传达我们希望使用IDisposable尽可能清除资源的观点,并认真尝试寻找使用终结器的方法。这符合应用程序的最大利益。
回答
IDisposable模式主要是由开发人员创建的,如果我们有一个实现IDispose的对象,则开发人员应该在对象的上下文周围实现" using"关键字,或者直接调用Dispose方法。
模式的故障保护是实现终结器,调用Dispose()方法。如果不这样做,则可能会造成一些内存泄漏,即:如果创建一些COM包装程序并且从不调用System.Runtime.Interop.Marshall.ReleaseComObject(comObject)(将其放置在Dispose方法中)。
除了跟踪包含终结器的对象并由GC将它们存储在Finalizer表中,然后由GC进行一些清理启发式调用时,clr中自动调用Dispose方法没有什么魔术。
回答
这里已经有很多很好的讨论了,我参加聚会有点晚了,但是我想自己补充一点。
- 垃圾收集器将永远不会直接为我们执行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; } } }
那是简单的版本,但是有很多细微差别可以使我们陷入这种模式。
- 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); } }
允许我们将包含类型简化为:
class SomeObject : IDisposable { SomeSafeHandle _SomeSafeHandle; FileStream _SomeFileStream; // Something useful here public virtual void Dispose() { _SomeSafeHandle.Dispose(); _SomeFileStream.Dispose(); } }