IDisposable
介面是一個用於約定可進行釋放資源操作的介面,一個類實現該介面則意味著可以使用介面約定的方法Dispose
來釋放資源。其定義如下:public interface IDisposable
{
void Dispose();
}
IDisposable
介面來對資源釋放做出約定——當程式設計師看到一個類實現IDisposable
介面時,就應該想到在使用完該類的範例後就應該呼叫其Dispose
方法來及時釋放資源。IDispose
介面的類,在C#中你通常可以採用如下方式來釋放資源:UnmanagedResource resource = /* ... */;
try
{
// 各種操作
}
finally
{
resource.Dispose();
}
(注:在finally中釋放是為了確保即便執行時出錯也可以順利釋放資源)
2:using
using (UnmanagedResource resource = /* ... */)
{
// 離開using的作用域後會自動呼叫resource的Dispose方法
}
// 或者如果不需要額外控制作用域的簡寫
using UnmanagedResource resource = /* ... */;
IDisposable
很容易實現,畢竟它只有一個方法需要實現,並且看上去只要在方法裡釋放掉需要釋放的資源即可:class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 釋放需要釋放的資源
}
}
Disposable
介面的類的範例呼叫Dispose
方法,但是,出於各種原因,或許是他是一名新手,或許他受到老闆的催促,或許他昨天沒睡好等等,這些都可能導致他沒有仔細檢查自己的程式碼。永遠不要假設你的程式碼會被一直正確地使用,總得留下些兜底的東西,提高健壯性——把你的使用者當做一個做著布朗運動的白痴,哪怕他可能是個經驗豐富的程式設計師,甚至你自己。
Dispose
方法釋放資源,就留著讓GC來呼叫釋放。還好,C#允許你讓GC來幫助你呼叫一些方法——通過終端子。~
。如下:class UnmanagedResource : IDisposable
{
// UnmanagedResource的終端子
~UnmanagedResource()
{
// 一些操作
}
}
class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 釋放需要釋放的資源
}
~UnmanagedResource()
{
// 終端子呼叫Dispose釋放資源
Dispose();
}
}
Dispose
方法後,並不表示你就告訴了GC不要再呼叫它的終端子,實際上,在你呼叫Dispose
方法後,GC還是會在某一時刻呼叫終端子,而由於我們在終端子裡呼叫了Dispose
方法,這會導致Dispose
方法再次被呼叫——Double Free!Dispose
方法裡檢查這個欄位的值,一旦發現已經釋放則過就立刻返回。如下:class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 如果已經釋放過就立刻返回
if (_disposed)
{
return;
}
// 釋放需要釋放的資源
// 標記已釋放
_disposed = true;
}
~UnmanagedResource()
{
Dispose();
}
// 用於標記是否已經釋放的欄位
private bool _disposed;
}
Dispose
重複呼叫是安全的。不過,要知道終端子是會影響效能的,因此為了效能考慮,我們還是希望在Dispose
方法呼叫後阻止終端子的執行(畢竟這時候已經不需要GC兜底了)。而要實現這一目標十分簡單,只需要在Dipose
方法中使用GC.SuppressFinalize(this)
告訴GC不要呼叫終端子即可。如下:class UnmanagedResource : IDisposable
{
public void Dispose()
{
if (_disposed)
{
return;
}
// 釋放需要釋放的資源
_disposed = true;
// 告訴GC不要呼叫當前範例(this)的終端子
GC.SuppressFinalize(this);
}
~UnmanagedResource()
{
Dispose();
}
private bool _disposed;
}
Dispose
方法,就會「抑制」GC對終端子的呼叫;而讓終端子呼叫Dispose
也不會產生什麼問題。class UnmanagedResource : IDisposable
{
// 其他程式碼
private FileStream _fileStream;
}
FileStream
是一個實現了IDisposable
的類,也就是說,FileStream
也需要進行釋放。UnmanagedResource
不僅要釋放自己的非託管資源,還要釋放FileStream
。你或許認為只需要在UnmanagedResource
的Dispose
方法中呼叫一下FileStream
的Dispose
方法就行。如下:class UnmanagedResource : IDisposable
{
// 其它程式碼
public void Dispose()
{
// 其他程式碼
_fileStream.Dispose();
// 其它程式碼
}
private FileStream _fileStream;
}
UnmanagedResource
的Dispose
方法是由終端子呼叫的會發生什麼?FileStream
的終端子先被呼叫了,執行過了其Dispose
方法釋放資源,隨後UnmanagedResource
的終端子呼叫Dispose
方法時會再次呼叫FileStream
的Dispose
方法——Double Free, Again。Dispose
方法是由終端子呼叫的,就不應該手動釋放那些本身就實現了終端子的託管資源——這些資源的終端子很可能先被執行。僅當手動呼叫Dispose
方法時才手動釋放那些實現了終端子的託管資源。Dispose
方法,用一個引數來指示Dispose
是否釋放託管資源。稍作調整,實現如下:class UnmanagedResource : IDisposable
{
// 其它程式碼
private void Dispose(bool disposing)
{
// 其他程式碼
if (disposing)
{
// 釋放託管資源
_fileStream.Dispose();
}
// 釋放非託管資源
// 其它程式碼
}
}
disposing
引數的Dispose(bool disposing)
方法,當disposing
為true
時,同時釋放託管資源和非託管資源;當disposing
為false
時,僅釋放託管資源。另外,為了不公開不必要的介面,將其宣告為private
。Dispose
方法和終端子中按需呼叫Dispose(bool disposing)
方法即可。class UnmanagedResource : IDisposable
{
// 其它程式碼
public void Dispose()
{
// disposing=true,手動釋放託管資源
Dispose(true);
GC.SuppressFinalize(this);
}
~UnmanagedResource()
{
// disposing=false,不釋放託管資源,交給終端子釋放
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// 釋放託管資源
}
// 釋放非託管資源
_disposed = true;
}
}
UnmanagedResource
的子類:class HandleResource : UnmanagedResource
{
private HandlePtr _handlePtr;
}
HandleResource
有自己的資源HandlePtr
,顯然如果只是簡單繼承UnmanagedResource
的話,UnmanagedResource
的Dispose
方法並不能釋放HandleResource
的HandlePtr
。UnmanagedResource
的Dispose
方法宣告為virtual
並在HandleResource
裡覆寫;或者在HandleResource
裡使用new
重新實現Dispose
似乎都可以:// 使用多型
class UnmanagedResource : IDisposable
{
public virtual void Dispose() { /* ... */}
}
class HandleResource : UnmanagedResource
{
public override void Dispose() { /* ... */}
}
// 重新實現
class UnmanagedResource : IDisposable
{
public void Dispose() { /* ... */}
}
class HandleResource : UnmanagedResource
{
public new void Dispose() { /* ... */}
}
HandleResource
重複做那些在它的父類別UnmanagedResource
做過的事——解決重複釋放、定義終端子以及區分對待託管和非託管資源。這太不「繼承了」——顯然,有更好的實現方法。
UnmanagedResource
的的Dispose(bool disposing)
方法存取許可權更改為protected
,並修飾為virtual
,以讓子類存取/覆蓋:class UnmanagedResource : IDisposable
{
protected virtual void Dispose(bool disposing) { /* ... */ }
}
Dispose(bool disposing)
來實現自己想要的釋放功能:class UnmanagedResource : IDisposable
{
protected override void Dispose(bool disposing)
{
// 其他程式碼
base.Dispose(disposing);
}
}
Dispose(bool disposing)
是虛方法,因此父類別UnmanagedResource
的終端子和Dispose
方法中對Dispose(bool disposing)
的呼叫會受多型的影響,呼叫到正確的釋放方法,故子類可以不必再做那些重複工作。
class UnmanagedResource : IDisposable
{
// 對IDisposable介面的實現
public void Dispose()
{
// 呼叫Dispose(true),同時釋放託管資源與非託管資源
Dispose(true);
// 讓GC不要呼叫終端子
GC.SuppressFinalize(this);
}
// UnmanagedResource的終端子
~UnmanagedResource()
{
// 呼叫Dispose(false),僅釋放非託管資源,託管資源交給GC處理
Dispose(false);
}
// 釋放非託管資源,並可以選擇性釋放託管資源,且可以讓子類覆寫的Dispose(bool disposing)方法
protected virtual void Dispose(bool disposing)
{
// 防止重複釋放
if (_disposed)
{
return;
}
// disposing指示是否是否託管資源
if (disposing)
{
// 釋放託管資源
}
// 釋放非託管資源
// 標記已釋放
_disposed = true;
}
}
【1】:IDisposable 介面
【2】:實現 Dispose 方法