关于C#Dispose Pattern的具体问题

Dav*_*ave 8 c# dispose idisposable

我有一些关于C#中的Dispose模式的基本问题.

在下面的代码片段中,您似乎是实现dispose模式的标准方法,您会注意到如果disposing为false,则不会处理托管资源.他们如何/何时处理?GC是否会出现并在以后处理托管资源?但如果是这样的话,GG.SuppressFinalize(this)调用会做什么?有人能给我一个处理托管资源的例子吗?想到了解开事件的想法.还要别的吗?编写模式的方式,如果你在"if(disposing)"部分没有做任何事情,它们似乎会被处理掉(稍后).评论?

protected virtual void Dispose(bool disposing)
{  
    if (!disposed)
    {
        if (disposing)
        {
            // Dispose managed resources.
        }

        // There are no unmanaged resources to release, but
        // if we add them, they need to be released here.
    }
    disposed = true;

    // If it is available, make the call to the
    // base class's Dispose(Boolean) method
    base.Dispose(disposing);
}
// implements IDisposable
public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}
Run Code Online (Sandbox Code Playgroud)

我在这个帖子中读到Dispose(bool)中的锁是否正确,如何在包装Interop COM对象时在c#中实现dispose模式??它说,"Meta-meta评论 - 以及在你的非管理清理期间你永远不会获得锁或使用锁定是很重要的."为什么会这样?它是否也适用于非托管资源?

最后,在没有实现IDisposable的情况下,是否实现了终结器(在C#中为~MyClass())?我相信如果没有非托管资源,我会在某处读到终结器和IDisposable不是必需的(或者是可取的).但是,我确实看到在一些例子中使用没有IDisposable的终结器(参见:http://www.codeproject.com/KB/cs/idisposable.aspx作为一个例子)谢谢,戴夫

Dir*_*mar 6

这种实现IDisposable模式的方法是一种故障安全方式:如果客户端忘记调用Dispose,运行时调用的终结器将Dispose(false)稍后调用(请注意,样本中缺少此部分).

在后一种情况下,即当Dispose终结器调用时,已经清理了托管资源,否则所讨论的对象将不具备垃圾收集的条件.

但如果是这样的话,GC.SuppressFinalize(this)调用会做什么?

运行终结器需要额外的费用.因此,如果可能,应该避免.调用GC.SuppressFinalize(this)将跳过运行终结器,因此可以更有效地对对象进行垃圾回收.

一般来说,应该避免依赖终结器,因为不能保证终结器能够运行.Raymond Chen在以下文章中描述了终结器的一些问题:

我什么时候需要使用GC.KeepAlive?

  • 一个小的挑剔:*"在后一种情况下...管理的资源已经被清理了"*.情况不一定如此:GC是非确定性的,所以可能它们已经被清理过,也许不是.无论哪种方式,你应该表现得好像*他们已经被清理过了(也就是说,你不应该试图对它们做任何事情). (2认同)

Han*_*ant 6

没有人得到最后两个问题(顺便说一下:每个线程只询问一个).在Dispose()中使用锁定对终结器线程非常致命.锁定可能持有的时间没有上限,当CLR注意到终结器线程卡住时,程序将在两秒钟后崩溃.而且,这只是一个bug.当另一个线程可能仍然具有对该对象的引用时,您永远不应该调用Dispose().

是的,在没有实现IDisposable的情况下实现终结器并不是闻所未闻.任何COM对象包装器(RCW)都可以.Thread类也是如此.这样做是因为调用Dispose()是不切实际的.对于COM包装器,因为无法跟踪所有引用计数.在Thread的情况下,因为必须Join()线程,以便您可以调用Dispose()失败了拥有一个线程的目的.

注意Jon Hanna的帖子.99.99%的时间实现自己的终结器确实是错误的.你有SafeHandle类来包装非托管资源.你需要一些非常模糊的东西,不能被它们包裹起来.


Jon*_*nna 5

上述模式是雄辩地处理处置和最终确定的重叠问题的问题。

当我们处置时,我们希望:

  1. 处理掉所有一次性成员物品。
  2. 处置基础对象。
  3. 释放非托管资源。

最终确定时,我们希望:

  1. 释放非托管资源。

除此之外还有以下问题:

  1. 多次调用处置应该是安全的。调用应该不会出错x.Dispose();x.Dispose();
  2. 最终确定增加了垃圾收集的负担。如果我们可以避免它,特别是如果我们已经释放了非托管资源,我们希望抑制终结,因为不再需要它。
  3. 访问最终对象是充满挑战的。如果一个对象正在被终结,那么任何可终结的成员(也将处理与我们的类相同的问题)可能已经或可能尚未被终结,并且肯定会在终结队列中。由于这些对象也可能是托管的一次性对象,并且处置它们将释放其非托管资源,因此我们不希望在这种情况下处置它们。

您提供的代码将(一旦您添加了调用Dispose(false)管理这些问题的终结器)。在被调用的情况下,Dispose()它将清理托管和非托管成员并抑制终结,同时还防止多次调用(但它不是线程)在这方面是安全的)。在调用终结器的情况下,它将清理非托管成员。

但是,只有在同一类中组合托管和非托管关注点的反模式才需要此模式。更好的方法是通过仅与该资源相关的类来处理所有非托管资源,无论是 SafeHandle 还是您自己的单独类。那么你将有两种模式之一,其中后者很少见:

public class HasManagedMembers : IDisposable
{
   /* more stuff here */
   public void Dispose()
   {
      //if really necessary, block multiple calls by storing a boolean, but generally this won't be needed.
      someMember.Dispose(); /*etc.*/
   }
}
Run Code Online (Sandbox Code Playgroud)

它没有终结器,也不需要终结器。

public class HasUnmanagedResource : IDisposable
{
  IntPtr _someRawHandle;
  /* real code using _someRawHandle*/
  private void CleanUp()
  {
     /* code to clean up the handle */
  }
  public void Dispose()
  {
     CleanUp();
     GC.SuppressFinalize(this);
  }
  ~HasUnmanagedResource()
  {
     CleanUp();
  }
}
Run Code Online (Sandbox Code Playgroud)

这个版本非常罕见(甚至在大多数项目中都不会发生),其处置处理仅用于处理唯一的非托管资源,该类是该资源的包装器,并且如果处置没有发生,则终结器会执行相同的操作。

由于 SafeHandle 允许为您处理第二种模式,因此您根本不需要它。无论如何,我给出的第一个例子将处理绝大多数需要实现的情况IDisposable。示例中给出的模式只能用于向后兼容,例如当您从使用它的类派生时。