仅限受管资源的最小IDisposable implimenation

时间:2013-09-23 23:50:36

标签: c# dispose idisposable

关于处理非托管资源的“标准完整”IDisposable实现有很多信息 - 但实际上这种情况非常罕见(大多数资源已经被托管类包装)。这个问题集中在更常见的“仅限托管资源”案例的IDisposable的最小实现上。

1:下面代码中的IDisposable的mimimal实现是否正确,是否存在问题?

2:有没有理由在最低限度的实施中添加完整标准IDisposable实施(Dispose()Dispose(bool)Finalizer等)?

3:在这个极小的情况下,使Dispose虚拟(因为我们没有提供Dispose(bool))是否可行/明智?

4:如果这个最小的实现被一个包含(无用的,在这种情况下)终结器的完整标准实现替换 - 这会改变GC处理对象的方式吗?有什么缺点吗?

5:该示例包括Timer和事件处理程序,因为这些情况特别重要,不要错过,因为未能处理它们会使对象保持活跃和踢(this在{{1}的情况下在事件处理程序的情况下,Timer,直到GC轮到它的时间处理它们。还有其他这样的例子吗?

eventSource

参考文献:
MSDN
SO: When do I need to manage managed resources
SO: How to dispose managed resource in Dispose() method in C#
SO: Dispose() for cleaning up managed resources

3 个答案:

答案 0 :(得分:7)

  1. 如果没有派生类直接拥有非托管资源,那么实现是正确的,没有问题。

  2. 实施完整模式的一个很好的理由是“最少惊喜的原则”。由于MSDN中没有描述这种简单模式的权威文档,维护开发人员可能会有疑虑 - 即使您觉得有必要问StackOverflow:)

  3. 是的,在这种情况下,Dispose可以是虚拟的。

  4. 如果调用Dispose并且正确实现(即调用GC.SuppressFinalize),则不必要的终结器的开销可以忽略不计

  5. .NET Framework本身以外的绝大多数IDisposable类都是IDisposable,因为它们拥有托管的IDisposable资源。他们很少直接持有非托管资源 - 这只有在使用P / Invoke访问.NET Framework未公开的非托管资源时才会发生。

    因此,推广这种简单模式可能是一个很好的理由:

    • 在极少数情况下使用非托管资源时,应将它们包装在密封的IDisposable包装类中,该类实现终结器(如SafeHandle)。因为它是密封的,所以这个类不需要完整的IDisposable模式。

    • 在所有其他情况下,绝大多数情况下,您可以使用更简单的模式。

    但除非并且直到微软或其他权威人士积极宣传它,否则我将继续使用完整的IDisposable模式。

答案 1 :(得分:2)

另一个选择是重构您的代码以避免继承并使您的IDisposable类密封。然后更简单的模式很容易证明,因为不再需要支持可能继承的笨拙的旋转。我个人大部分时间都采用这种方法;在极少数情况下,我想制作一个非密封的一次性用品,我只是遵循'标准'模式。培养这种方法的一个好处是,它倾向于将你推向组合而不是继承,这通常使代码更容易维护和测试。

答案 2 :(得分:0)

我推荐的Dispose模式适用于链接到虚拟Dispose的非虚拟void Dispose(bool)实现,最好是在以下内容之后:

int _disposed;
public bool Disposed { return _disposed != 0; }
void Dispose()
{
  if (System.Threading.Interlocked.Exchange(ref _disposed, 1) != 0)
    Dispose(true);
  GC.SuppressFinalize(this); // In case our object holds references to *managed* resources
}

使用此方法将确保Dispose(bool)仅被调用一次,即使多个线程尝试同时调用它也是如此。虽然这种同时处置的尝试很少见(*),但要防范它们是便宜的;如果基类没有像上面那样做,那么每个派生类必须有自己的冗余双配置保护逻辑,并且可能也是冗余标志。

(*)一些通常是单线程且使用阻塞I / O的通信类允许从任何线程上下文调用Dispose来取消阻塞其自己的线程的I / O操作[显然{ {1}}无法在该线程上调用,因为该线程在被阻止时无法任何 。这些对象或封装它们的对象完全可能 - 而且不是不合理 - 让外部线程尝试Dispose它们作为一种在他们当前中止当前操作的方法将由他们的主线处理。同时Dispose电话可能很少见,但它们的可能性并不表示任何设计问题"只要Dispose代码可以对一个呼叫起作用而忽略另一个呼叫。