确定性终结模板
很明显,实现安全的Dispose()和Finalize()涉及很多细节,特别是涉及继承的时候。下面,提供一个通用的模板。
在类层上实现Dispose()和Finalize()的模板
{
private bool m_Disposed = false;
protected bool Disposed
{
get
{
lock(this)
{
return m_Disposed;
}
}
}
//不要将Dispose()置成virtual,应该保护它不被子类重载
public void Dispose()
{
lock(this)
{
//检查Dispose()是否已经被调用
if (m_Disposed == false)
{
Cleanup();
m_Disposed = true;
//从析构化队列中移除
//从而保证不被二次执行
GC.SuppressFinalize(this);
}
}
}
protected virtual void Cleanup()
{
/*在这里进行清理工作*/
}
//析构函数只有在Dispose()没有被调用的时候才运行
//不要为从这个类派生的类型提供析构函数
~BaseClass()
{
Cleanup();
}
public void DoSomething()
{
if(Disposed) //在每个方法中都校验
{
throw new ObjectDisposedException("Object is already disposed");
}
}
}
public class SubClass1 : BaseClass
{
protected override void Cleanup()
{
try
{
/*在这里进行清理工作*/
}
finally
{
//调用基类
base.Cleanup();
}
}
}
public class SubClass2 : SubClass1
{
protected override void Cleanup()
{
try
{
/*在这里进行清理工作*/
}
finally
{
//调用基类
base.Cleanup();
}
}
}
类层次结构的每层都会在Cleanup()方法中实现自己的资源清理代码。调用IDisposable.Dispose()或是析构函数(Finalize()方法)都被转到 Cleanup()方法。只有类层中顶端基类实现Idisposable,使得所有的子类都是IDisposable的多态。另一方面,顶端基类实现了一个非虚拟的Dispose()方法,这样能阻止子类重载它,顶端基类的IDisposable.Dispose()实现调用了Cleanup()。Dispose()在任何时候只能被单个线程调用,因为它使用了同步锁。这样能预防两个线程同时试图销毁对象,顶端基类维护一个布尔标记——m_Disposed,它能反映Dispose()是否已被调用。当Dispose()第一次被调用后,m_Disposed标记为true,这可以防止Cleanup()被再次调用。这样一来,多次调用Dispose()是无害的。
最顶层的基类提供了一个线程安全的、只读的Disposed属性,其他基类或子类中的所有方法在执行方法体以前,须检查该属性,如果Dispose()已被调用,则抛出一个ObjectDisposedException异常。
注意,Cleanup()不仅是虚拟的,而且是受保护的,虚拟的目的在于让子类能重载它,受保护的目的是防止客户端使用它。类层中每种层次的类如果有东西需要清理,都应实现自己的Cleanup()方法。还要注意的是,只有最顶端的基类才必须含有析构函数。析构函数所做的只是委托到虚拟的、受保护的Cleanup()。如果Dispose()先被调用,析构函数是不会被调用的,因为Dispose()抑制了终结。通过析构函数调用Cleanup()和通过Dispose()调用Cleanup()的区别在于布尔参数m_Disposed,它可以使Dispose()知道是否该抑制终结。
以下是模板的运行机制:
1 客户端在类层中创建并使用一个对象,然后通过IDisposable来调用Dispose(),或直接调用Dispose()。
2 无论该对象在类层次结构的哪一级创建,它都通过顶端基类,它调用虚拟的Cleanup()方法。
3 该调用走到最低的子类,并调用它的Cleanup()方法。因为类层中的每个级别的类都调用基类的Cleanup()方法,这样每个级别的类都执行了自己的清理。
4 如果客户端没有调用Dispose(),析构函数就调用Cleanup()方法。
要注意的是,这个模板正确处理了所有可能的类型情况:变量类型、实例化类型和类型转化。
SubClass1 a = new SubClass2();
a.Dispose();
SubClass1 b = new SubClass2();
((SubClass2)b).Dispose();
IDisposable c = new SubClass2();
c.Dispose();
SubClass2 d = new SubClass2();
((SubClass1)d).Dispose();
SubClass2 e = new SubClass2();
e.Dispose();