在我的终结器中处理我的System.IDisposable对象

时间:2015-08-03 07:13:16

标签: c# dispose idisposable finalizer

StackOverflow上有几个关于如果我的对象管理实现System.IDisposable的其他托管对象该怎么做的讨论。

注意:下面我谈论非托管代码。我完全理解清理非托管代码的重要性

大多数讨论都说如果你的对象拥有另一个实现System.IDisposable的托管对象,那么你也应该实现System.IDisposable,在这种情况下你应该调用一次性Dispose()对象所持有的对象。这是合乎逻辑的,因为您不知道您拥有的一次性对象是否使用非托管代码。您只知道另一个对象的创建者认为如果您不再需要该对象时立即调用Dispose将是明智的。

在社区维基编辑的StackOverflow上给出了对Disposable模式的一个非常好的解释:

Proper use of the IDisposable interface

很多时候,我也读过上面提到的链接:

  

“你不知道两个对象被销毁的顺序。完全有可能在你的Dispose()代码中,你试图摆脱的托管对象不再存在。”< / p>

这让我感到困惑,因为我认为只要任何对象持有对象X的引用,那么对象X就不会也无法完成。

或换句话说:只要我的对象持有对象X的引用,我就可以确定对象X没有最终确定。

如果这是真的,那么为什么会这样,如果我在完成之前持有对象的引用,那么我引用的对象已经完成了?

5 个答案:

答案 0 :(得分:5)

真相介于两者之间:

  • 对象不能被垃圾收集,因此对象不再“存在”的可能性不是真的

如果对象X引用对象Y,但两者都是可终结的,那么对象Y完全可以在对象X之前完成,或者甚至可以同时完成它们。

如果你的假设是正确的,那么你可以创建两个相互引用的对象(并有终结器),并且它们永远不会被垃圾收集,因为它们永远无法完成。

答案 1 :(得分:1)

引用Eric Lippert'sWhen everything you know is wrong, part two

  

神话:保持对变量中对象的引用会阻止   在变量处于活动状态时运行终结器;一个局部变量   至少直到控制离开其中的块为止   当地人被宣布。

{   
     Foo foo = new Foo();
     Blah(foo);  // Last read of foo
     Bar();
     // We require that foo not be finalized before Bar();
     // Since foo is in scope until the end of the block,
     // it will not be finalized until this point, right?
}
     

C#规范声明运行时允许宽范围   检测何时永远不会访问包含引用的存储   再次,并停止将该存储视为垃圾收集器的根。   例如,假设我们有一个局部变量foo并且写了一个引用   进入它的顶部。如果抖动知道特定读取是   最后一次读取该变量,该变量可以合法地从中删除   立即设置GC根; 它不必等到控制离开   变量的范围。如果该变量包含最后一个引用,那么   GC可以检测到对象无法访问并将其放在终结器队列中   立即。使用GC.KeepAlive来避免这种情况。

     

为什么抖动有这个范围?假设局部变量是   注册到寄存器需要将值传递给Blah()。如果foo   在Bar()需要使用的寄存器中,没有必要保存   foo之前堆栈中永远不会再读Bar()的值   调用。 (如果抖动生成的代码的实际细节是   你感兴趣,see Raymond Chen’s deeper analysis of this issue。)

     

额外奖励乐趣:运行时使用不太激进的代码生成和   在运行程序时不太积极的垃圾收集   调试器,因为拥有对象是一个糟糕的调试经验   即使变量,你正在调试突然消失   指的是对象在范围内。这意味着如果你有一个bug   对象过早定型的地方,你可能不会   在调试器中重现该错误!

     

请参阅本文的最后一点,了解更为可怕的版本   这个问题。

答案 2 :(得分:0)

如果操作正确,您不必担心处置已经处置过的物体。 Dispose的每一个实现都应该在以前处理之前不做任何事情。

实际上,您无法知道是否已经处理或最终确定了任何子对象(因为终结的顺序是随机的,请参阅其他帖子),但您仍然可以安全地调用它们的Dispose方法。

答案 3 :(得分:0)

在所有答案之后,我创建了一个小程序,显示Jodrell写的内容(谢谢Jodrell!)

  • 即使我有参考对象,也可以在不使用对象时立即进行垃圾回收
  • 只有在不调试时才会这样做。

我写了一个简单的类来分配非托管内存和一个MemoryStream。后者实现了System.IDisposable。

根据StackOverflow上的每个人,我应该实现System.IDisposable并释放非托管内存以及如果我的Dispose被调用则Dispose托管memoryStream,但是如果我的终结器被调用,我应该只释放非托管内存。

我写了一些诊断控制台消息

class ClassA : System.IDisposable
{
    IntPtr memPtr = Marshal.AllocHGlobal(1024);
    Stream memStream = new MemoryStream(1024);

    public ClassA()
    {
        Console.WriteLine("Construct Class A");
    }

    ~ClassA()
    {
        Console.WriteLine("Finalize Class A");
        this.Dispose(false);
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose()");
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        Console.WriteLine("Dispose({0})", disposing.ToString());
        if (!this.IsDisposed)
        {
            if (disposing)
            {
                Console.WriteLine("Dispose managed objects");
                memStream.Dispose();
            }

            Console.WriteLine("Dispose unmanaged objects");
            Marshal.FreeHGlobal(memPtr);                
        }
    }

    public bool IsDisposed { get { return this.memPtr == null; } }
}

该程序遵循Dispose Pattern,如所描述的那样,a.o。这里是Proper use of the IDisposable interface

中的stackoverflow 顺便说一下:为简单起见,我省略了异常处理

一个简单的控制台程序创建对象,不使用它,但保留对它的引用并强制垃圾收集器收集:

private static void TestFinalize()
{
    ClassA a = new ClassA() { X = 4 };

    Console.WriteLine("Start Garbage Collector");
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine("Done");
}

请注意,变量a保存对象的引用,直到过程结束。我忘了Dispose,所以我的终结者应该处理

从您的主要方式调用此方法。从调试器运行(release)构建并通过命令提示符运行它。

  • 如果从调试器运行,则对象保持活动状态直到过程结束,因此直到垃圾收集器完成收集后
  • 如果从命令提示符运行,则在过程结束之前完成对象,即使我仍然有对该对象的引用。
  

所以Jodrell是对的:

     
      
  • 非托管代码需要Dispose()和Finalize,使用Dispose(bool)

  •   
  • 托管一次性对象需要Dispose(),最好通过Dispose(bool)。在Dispose(bool)中,只有在处理

  • 时才调用托管对象的Dispose()   
  • 不信任调试器:它使得对象在不同时刻完成而不是没有调试器

  •   

答案 4 :(得分:0)

在大多数情况下,对包含对一个或多个Finalize个对象的引用的对象调用IDisposable时,将应用以下一个或多个:

  1. 另一个对象已经被清除,在这种情况下,调用Dispose最好无用。
  2. 另一个对象已经安排尽快完成,但还没有,在这种情况下,可能不需要调用Dispose
  3. 其他对象的清理代码无法在终结器线程上下文中安全使用,在这种情况下,调用Dispose可能是灾难性的。
  4. 其他对象仍被其他地方的代码使用,在这种情况下调用Dispose可能是灾难性的。
  5. 在某些情况下,代码对其所处理的IDisposable个对象有足够的了解,知道上述任何一个都不适用,或者尽管如此,它应该触发清理;但是,通过让其他对象提供一个方法而不是Dispose ,最终确定的对象可以调用这些情况,可以更好地满足这些情况。