在.NET中覆盖Dispose(bool disposing)的意义是什么?

48
如果我在C#中编写一个实现IDisposable的类,为什么仅仅实现它是不够的?

public void Dispose(){ ... } 

如何处理释放任何未受管控的资源?

是什么?

protected virtual void Dispose(bool disposing){ ... }

总是必要的、有时必要的,还是完全不必要的?


在我的类中,我只有需要清理的非托管资源,并且我正在实现IDisposable接口,其中包含受保护的虚拟void Dispose(bool disposing)方法,因为静态分析要求这样做。在我的情况下,“disposing”参数没有用处,因为我希望Finalizer和Dispose执行相同的操作。 - AksharRoop
8个回答

49

完整的模式包括终结器、引入新的虚拟方法以及"封口"原始dispose方法,非常通用,覆盖所有基础。

除非你有对非托管资源的直接句柄(几乎从不),否则你不需要终结器。

如果你封口了你的类(而我对于尽可能封口类的看法可能已经广为人知 - 为继承设计或禁止它),那么引入虚拟方法就没有意义了。

我不记得上一次是以"复杂"的方式实现IDisposable还是以最明显的方式实现它了,例如:

public void Dispose()
{
    somethingElse.Dispose();
}

需要注意的一点是,如果你想要编写非常健壮的代码,应该确保在被销毁后不再执行任何操作,并在适当的情况下抛出ObjectDisposedException。这是给全世界的开发者使用的类库的好建议,但如果仅在自己的工作区中使用,则为了获得很少的收益而付出了很多的努力。


1
很棒的解释——让人松了一口气! - Jon Coombs
你的bluebyte链接已经失效,请注意。 - Hamish Smith

34

这不是必须的。它是推荐的可清除模式的一部分。如果您还没有阅读过《框架设计指南》中关于此问题的章节(第一版的9.3,很抱歉没有第二版的资料),那么建议您先阅读。尝试这个链接

将可清除的清理和需要进行终结器垃圾回收区分开来非常有用。

您不一定非得按照这种方式去做,但在将其视为不必要之前,建议您先了解为什么推荐这样做,并阅读相关内容。


4
“IDisposable: What Your Mother Never Told You About Resource Deallocation”的链接太远了,所以我在这里重复一遍:http://www.codeproject.com/KB/dotnet/IDisposable.aspx。 - Roman Starkov
FYI上面的第一个链接到bluebytesoftware现在已经失效。 - codenheim
@mrjoltcola 谢谢,已更新为 MSDN 链接(但它们也可能有点短暂)。 - Hamish Smith

16

微软文档中有一些关于可释放模式的偏见。有两个原因你应该实现IDisposable:

  1. 你有一个实现了IDisposable接口类型的字段。
  2. 你有一个finalizer(终结器)。

第1种情况在大多数代码中非常普遍。第2种情况在微软编写的代码中非常普遍,他们编写了管理封装非托管资源的代码,这些代码需要进行终止处理。但在你的代码中这种情况应该非常罕见。毕竟,你有所有这些好用的.NET类来为你完成繁琐的工作。你只需要调用它们的Dispose()方法即可。

只有第2种情况需要使用可释放模式。Microsoft需要经常使用它。而你大部分时间只需要简单的Dispose()方法。


在我的类中,我只有需要清理的非托管资源,并且我正在实现IDisposable接口,其中包含受保护的虚拟void Dispose(bool disposing)方法,因为静态分析要求这样做。在我的情况下,“disposing”参数没有用处,因为我希望Finalizer和Dispose执行相同的操作。 - AksharRoop
不确定那个评论想要表达什么。你可能做错了,应该使用SafeHandle和/或SafeBuffer包装类。关键的终结器很好用。回到第一点。 - Hans Passant
好的,我的原始问题是当值为true和值为false时,Dispose(bool disposing)应该做什么不同的事情。disposing参数有什么用? - AksharRoop
3
点击“提问”按钮以提出问题。 - Hans Passant
@HansPassant 你的意思是:点击浏览器顶部的地址栏,输入“www.google.com”,然后按回车键来提问。 - Sebastien

5

2
第一篇文章在简单的dispose情况下仍然包括“disposing”属性,这是无意义的。Jon在他的示例中提供了真正简单的情况,可能还加上“virtual”关键字。 - piers7

4

带有bool disposing参数的附加方法是从某个框架设计指南中提出的。这仅仅是一种模式,允许您的类拥有dispose方法可以被多次调用而不会抛出异常。它并非绝对必要。从技术上讲,您可以在dispose方法中完成此操作。


1

仅仅是为了扩展其他人所说的:不仅是你不需要“复杂的处理”,而且出于性能原因,你实际上不想要它。

如果你选择“复杂的处理”路线,并实现一个终结器,然后忘记显式地处理你的对象,那么你的对象(以及它引用的任何内容)将在真正处理之前多存活一代GC(因为它必须再多挂一次等CLR调用终结器)。这只会造成更多不必要的内存压力。此外,对整个堆中的对象调用终结器具有非微不足道的成本。

因此,除非你(或你的派生类型)拥有非托管资源,否则请避免使用“复杂的处理”。

哦,在我们讨论这个问题时:你类中处理来自其他类的事件的方法必须在该类被处理后被调用时“安全”。最简单的方法是,如果该类已被处理,则执行无操作。请参见http://blogs.msdn.com/ericlippert/archive/2009/04/29/events-and-races.aspx


1

它给你的一个好处是能够在Dispose()中执行与终结无关的工作,同时清理非托管资源。

在终结器中对托管对象进行任何操作都是极其不可预测的,除了对自己的操作。这主要是因为你的终结器将在AppDomain的第二阶段关闭时以非确定性的方式被调用,所以当你的终结器被调用时,你仍然有引用的对象很可能已经被终结了。

将Dispose和终结器调用分派到同一个方法中允许你共享关闭代码,而布尔参数允许你跳过托管清理(如果有的话)。

此外,该方法的虚拟性提供了一种简单的方式,使得继承者可以添加自己的清理代码,而不会意外地忘记调用你的代码。


1
如果一个类实现了IDisposable.Dispose()方法,而派生类需要添加额外的逻辑,那么该类必须公开某种Dispose方法,以便派生类可以链接到该方法。由于一些类可能会实现IDisposable.Dispose()方法而没有公共的Dispose()方法,因此有一个虚拟方法是很有用的,该方法将在所有IDisposable实现中都被保护,无论它们是否具有公共的Dispose方法。在大多数情况下,bool参数并不真正有意义,但应该将其视为虚拟方法protected virtual Dispose(bool)与可能是或可能不是公共的Dispose()具有不同的签名。

没有使用protected virtual Dispose(bool)的类将要求派生类以与约定不同的方式处理其清理逻辑。一些语言(如C++/CLI)只能扩展遵循该约定的IDisposable实现,可能无法从非标准实现派生类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接