仅用于受管资源的最小IDisposable启示
关于处理非托管资源的“标准完整” IDisposable
实现有很多信息 - 但实际上这种情况非常罕见(大多数资源已被托管类包装)。 这个问题关注于IDisposable的一个简单实现,用于更常见的“仅管理资源”情况。
1:以下代码中IDisposable
的最小实现是否正确,是否存在问题?
2:是否有任何理由添加完整的标准IDisposable
实现( Dispose()
, Dispose(bool)
, Finalizer
等)以提供最小的实现?
3:在这个最小的情况下可以/明智地使Dispose
虚拟(因为我们不提供Dispose(bool)
)?
4:如果这个最小的实现被包含一个(在这种情况下无用的)终结器的完整标准实现取代 - 这是否改变了GC如何处理对象? 有什么缺点吗?
5:示例包括Timer
和事件处理程序,因为这些情况下,尤其重要的是不能错过的无法处理它们会保持对象活蹦乱跳( this
在的情况下, Timer
, eventSource
事件处理程序的情况下),直到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()用于清理管理资源
实现是正确的,没有问题,只要派生类没有直接拥有非托管资源。
实施完整模式的一个很好的理由是“最少突击的原则”。 由于MSDN中没有描述这种简单模式的权威性文档,因此维护开发人员可能会怀疑 - 即使您觉得有必要问StackOverflow :)
是的,在这种情况下Dispose是虚拟的。
如果Dispose被调用并且被正确实现(即调用GC.SuppressFinalize),那么不必要的终结器的开销可以忽略不计。
.NET Framework之外的绝大多数IDisposable
类本身都是IDisposable
因为它们拥有托管的IDisposable
资源。 他们很少直接持有非托管资源 - 这种情况只有在使用P / Invoke访问未由.NET Framework公开的非托管资源时才会发生。
因此,推广这种更简单的模式可能有一个很好的理由:
在使用非托管资源的罕见情况下,它们应该封装在实现终结器(如SafeHandle)的密封IDisposable
包装类中。 由于它是密封的,这个类不需要完整的IDisposable模式。
在所有其他情况下,绝大多数情况下,可以使用更简单的模式。
但除非微软或其他权威人士积极宣传它,否则我将继续使用完整的IDisposable
模式。
另一种选择是重构代码以避免继承,并使您的IDisposable
类密封。 然后,更简单的模式很容易证明,因为支持可能继承的尴尬回转不再是必要的。 我个人大多数时候都采取这种方法; 在极少数情况下,我想要制作一个非密封类的一次性,我只是遵循'标准'模式。 培养这种方法的一个好处是,它倾向于将你推向组合而不是继承,这通常会使代码更易于维护和测试。
我推荐的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操作[显然, Dispose
不能被调用线程,因为该线程在阻塞时不能执行任何操作]。 这是完全可能的-而不是不合理的-这样的对象,或者其封装对象,有一个外螺纹尝试Dispose
它们作为只是他们所要去的那一刻中止其当前操作的手段,以通过他们的主要处置线。 同时Dispose
呼叫可能很少,但是它们的可能性并不表示任何“设计问题”,只要Dispose
代码可以对一个呼叫采取行动而忽略另一个呼叫。
上一篇: Minimal IDisposable implimenation for managed resources only