确定性终结模板

很明显,实现安全的Dispose()和Finalize()涉及很多细节,特别是涉及继承的时候。下面,提供一个通用的模板。

在类层上实现Dispose()和Finalize()的模板

public class BaseClass: IDisposable 
{
    
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();

 

posted @ 2010-04-29 22:49  逆时针  阅读(295)  评论(0编辑  收藏  举报