12

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

1:下面代码中的最小实现IDisposable是否正确,有问题吗?

2:是否有任何理由在提供的最小 实现上添加完整的标准IDisposable实现(Dispose()Dispose(bool)等)?Finalizer

3:在这个最小的情况下制作Dispose虚拟(因为我们不提供Dispose(bool))是否可以/明智?

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

5:示例包括Timer和事件处理程序,因为这些情况特别重要,不要错过,因为未能释放它们将使对象保持活动状态(this在 的情况下TimereventSource在事件处理程序的情况下),直到 GC 开始处理它们是时候了。还有其他类似的例子吗?

class A : IDisposable {
    private Timer timer;
    public A(MyEventSource eventSource) {
        eventSource += Handler
    }

    private void Handler(object source, EventArgs args) { ... }

    public virtual void Dispose() {
        timer.Dispose();
        if (eventSource != null)
           eventSource -= Handler;
    }
}

class B : A, IDisposable {
    private TcpClient tpcClient;

    public override void Dispose() {
        (tcpClient as IDispose).Dispose();
        base.Dispose();
    }   
}

参考文献:
MSDN
SO:我什么时候需要管理托管资源
SO:如何在 C# 的 Dispose() 方法中处理托管资源
SO:Dispose() 用于清理托管资源

4

3 回答 3

8
  1. 实现是正确的,没有任何问题,前提是没有派生类直接拥有非托管资源。

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

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

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

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

因此,推广这种更简单的模式可能有一个很好的论据:

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

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

但是除非并且直到微软或其他一些权威来源积极推广它,否则我将继续使用完整的IDisposable模式。

于 2013-09-24T10:11:57.057 回答
2

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

于 2013-09-25T15:02:40.293 回答
0

我推荐Dispose的模式是让非虚拟Dispose实现链接到 virtual 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 操作[显然Dispose不能在该线程上调用,因为该线程在被阻塞时无法做任何事情]。对于此类对象或封装它们的对象,完全有可能(而且并非不合理)让外部线程尝试在Dispose它们将由主线程处理它们的那一刻作为中止当前操作的一种手段. 同时Dispose调用可能很少见,但它们的可能性并不表示任何“设计问题”,前提是Dispose代码可以对一个调用起作用而忽略另一个。

于 2013-09-24T20:15:36.223 回答