Effective C# Item18:实现标准Dispose模式
如果一个类型中包含了非托管的资源,那么我们应该自己编写释放非托管资源的方法。.NET提供了一个标准的用于释放资源的模式,叫做Dispose模式,在这种模式中,类型实现IDisposable接口,并提供一个终结器。这样,正常流程下类型的使用者调用Dispose()方法来释放资源,如果用户忘记调用Dispose()方法, 那么类型的终结器会作为最后的保障来释放对象的非托管资源。
在Dispose模式中,类层次中的根基类应该实现IDisposable接口来释放非托管资源,并且该类型应该添加一个终结器作为保障机制。同时这两个方法最后都要把自己释放资源的方法做成一个虚方法,这样派生类可以重写该虚方法来满足自己的资源管理需要,只有在派生类必须释放自己的资源时,它才需要重写基类中的虚方法,而且必须要调用基类的虚方法。
当垃圾回收器运行时,它会立即释放那些没有终结器的垃圾对象的内存。所有实现了终结器的对象都仍然存储在内存中,但是会添加到一个终结队列中,同时垃圾回收器会创建一个线程来运行这些对象上的终结器,在终结器线程完成它的工作后,这些垃圾对象的内存才有可能被彻底删除。这样需要终结操作的对象会比美元后终结器的对象在内存中停留的时间更长。
当我们实现IDisposable接口时,需要在Dispose()方法中完成以下任务:
- 释放所有的非托管资源。
- 释放所有的托管资源。
- 设置一个状态标记,表明这个对象已经执行过Dispose()方法。
- 取消对象的终结奥做需求,我们可以通过调用GC.SuppressFinalize(this)来实现。
在一个拥有完整类继承的层次结构中,我们可以按如下方式编写基类释放资源的代码。
1 public class MyResourceHog : IDisposable
2 {
3 // Flag for already disposed
4 private bool _alreadyDisposed = false;
5
6 // finalizer:
7 // Call the virtual Dispose method.
8 ~MyResourceHog()
9 {
10 Dispose( false );
11 }
12
13 // Implementation of IDisposable.
14 // Call the virtual Dispose method.
15 // Suppress Finalization.
16 public void Dispose()
17 {
18 Dispose( true );
19 GC.SuppressFinalize( true );
20 }
21
22 // Virtual Dispose method
23 protected virtual void Dispose( bool isDisposing )
24 {
25 // Don't dispose more than once.
26 if ( _alreadyDisposed )
27 return;
28 if ( isDisposing )
29 {
30 // TODO: free managed resources here.
31 }
32 // TODO: free unmanaged resources here.
33 // Set disposed flag:
34 _alreadyDisposed = true;
35 }
36 }
37
上述代码中,由于终结器和Dispose()方法都会有释放资源的需要,因此,将这部分代码单独提取成一个虚方法,这样派生类如果有自己释放资源的需求,可以重写这个方法。
子类的代码如下所示。
1 public class DerivedResourceHog : MyResourceHog
2 {
3 // Have its own disposed flag.
4 private bool _disposed = false;
5
6 protected override void Dispose( bool isDisposing )
7 {
8 // Don't dispose more than once.
9 if ( _disposed )
10 return;
11 if ( isDisposing )
12 {
13 // TODO: free managed resources here.
14 }
15 // TODO: free unmanaged resources here.
16
17 // Let the base class free its resources.
18 // Base class is responsible for calling
19 // GC.SuppressFinalize( )
20 base.Dispose( isDisposing );
21
22 // Set derived class disposed flag:
23 _disposed = true;
24 }
25 }
注意,上述代码中,基类和派生类中都包含有一个标记,来设置对象的释放状态,这就是一种防御策略,在类层次中重复使用这样的标记,可以把“对象释放过程中所出现的任何可能的错误”封装在类层次中的一个类型中,而不是组成对象的多个类型内。我们之所以需要这样做,是因为我们可能会碰到在实例被释放后调用Dispose()方法,如果一个实例已经被释放过了,那么在调用它的Dispose()方法或者Finalize()方法,应该什么都不做。
当我们在编写Dispose()方法或者Finalize()方法时,需要注意一点:这些方法应该只负责释放资源的工作,不应该包含其他业务逻辑。对于一个对象来说,它的生命周期始于构造函数,终于垃圾回收。在对象调用了Dispose()方法但是内存还没有被删除的这段时间里,我们可以认为对象处于“昏迷”状态,如果我们在Dispose()方法,那么很可能会将对象重新置为可用,这样会扰乱对象的生命周期。
我们来看下面的代码。
1 public class BadClass
2 {
3 // Store a reference to a global object:
4 private readonly ArrayList _finalizedList;
5 private string _msg;
6
7 public BadClass( ArrayList badList, string msg )
8 {
9 // cache the reference:
10 _finalizedList = badList;
11 _msg = (string)msg.Clone();
12 }
13
14 ~BadClass()
15 {
16 // Add this object to the list.
17 // This object is reachable, no
18 // longer garbage. It's Back!
19 _finalizedList.Add( this );
20 }
21 }
上述代码在析构函数中,又将对象本身放入到一个列表中,这样对象本身又变为可用的,这样做是不正确的。