如果一個類型中包含了非託管的資源,那麼我們應該自己編寫釋放非託管資源的方法。.NET提供了一個標準的用於釋放資源的模式,叫做Dispose模式,在這種模式中,類型實現IDisposable接口,並提供一個終結器。這樣,正常流程下類型的使用者調用Dispose()方法來釋放資源,如果用戶忘記調用Dispose()方法, 那麼類型的終結器會作爲最後的保障來釋放對象的非託管資源。
在Dispose模式中,類層次中的根基類應該實現IDisposable接口來釋放非託管資源,並且該類型應該添加一個終結器作爲保障機制。同時這兩個方法最後都要把自己釋放資源的方法做成一個虛方法,這樣派生類可以重寫該虛方法來滿足自己的資源管理需要,只有在派生類必須釋放自己的資源時,它才需要重寫基類中的虛方法,而且必須要調用基類的虛方法。
當垃圾回收器運行時,它會立即釋放那些沒有終結器的垃圾對象的內存。所有實現了終結器的對象都仍然存儲在內存中,但是會添加到一個終結隊列中,同時垃圾回收器會創建一個線程來運行這些對象上的終結器,在終結器線程完成它的工作後,這些垃圾對象的內存纔有可能被徹底刪除。這樣需要終結操作的對象會比美元后終結器的對象在內存中停留的時間更長。
當我們實現IDisposable接口時,需要在Dispose()方法中完成以下任務:
- 釋放所有的非託管資源。
- 釋放所有的託管資源。
- 設置一個狀態標記,表明這個對象已經執行過Dispose()方法。
- 取消對象的終結奧做需求,我們可以通過調用GC.SuppressFinalize(this)來實現。
在一個擁有完整類繼承的層次結構中,我們可以按如下方式編寫基類釋放資源的代碼。
public class MyResourceHog : IDisposable
{
// Flag for already disposed
private bool _alreadyDisposed = false;
// finalizer:
// Call the virtual Dispose method.
~MyResourceHog()
{
Dispose( false );
}
// Implementation of IDisposable.
// Call the virtual Dispose method.
// Suppress Finalization.
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( true );
}
// Virtual Dispose method
protected virtual void Dispose( bool isDisposing )
{
// Don't dispose more than once.
if ( _alreadyDisposed )
return;
if ( isDisposing )
{
// TODO: free managed resources here.
}
// TODO: free unmanaged resources here.
// Set disposed flag:
_alreadyDisposed = true;
}
}
上述代碼中,由於終結器和Dispose()方法都會有釋放資源的需要,因此,將這部分代碼單獨提取成一個虛方法,這樣派生類如果有自己釋放資源的需求,可以重寫這個方法。
子類的代碼如下所示。
public class DerivedResourceHog : MyResourceHog
{
// Have its own disposed flag.
private bool _disposed = false;
protected override void Dispose( bool isDisposing )
{
// Don't dispose more than once.
if ( _disposed )
return;
if ( isDisposing )
{
// TODO: free managed resources here.
}
// TODO: free unmanaged resources here.
// Let the base class free its resources.
// Base class is responsible for calling
// GC.SuppressFinalize( )
base.Dispose( isDisposing );
// Set derived class disposed flag:
_disposed = true;
}
}
注意,上述代碼中,基類和派生類中都包含有一個標記,來設置對象的釋放狀態,這就是一種防禦策略,在類層次中重複使用這樣的標記,可以把“對象釋放過程中所出現的任何可能的錯誤”封裝在類層次中的一個類型中,而不是組成對象的多個類型內。我們之所以需要這樣做,是因爲我們可能會碰到在實例被釋放後調用Dispose()方法,如果一個實例已經被釋放過了,那麼在調用它的Dispose()方法或者Finalize()方法,應該什麼都不做。
當我們在編寫Dispose()方法或者Finalize()方法時,需要注意一點:這些方法應該只負責釋放資源的工作,不應該包含其他業務邏輯。對於一個對象來說,它的生命週期始於構造函數,終於垃圾回收。在對象調用了Dispose()方法但是內存還沒有被刪除的這段時間裏,我們可以認爲對象處於“昏迷”狀態,如果我們在Dispose()方法,那麼很可能會將對象重新置爲可用,這樣會擾亂對象的生命週期。
我們來看下面的代碼。
public class BadClass
{
// Store a reference to a global object:
private readonly ArrayList _finalizedList;
private string _msg;
public BadClass( ArrayList badList, string msg )
{
// cache the reference:
_finalizedList = badList;
_msg = (string)msg.Clone();
}
~BadClass()
{
// Add this object to the list.
// This object is reachable, no
// longer garbage. It's Back!
_finalizedList.Add( this );
}
}
上述代碼在析構函數中,又將對象本身放入到一個列表中,這樣對象本身又變爲可用的,這樣做是不正確的。