如何實現標準的dispose

前面的文章我們說過,如果對象包含非託管資源那麼就必須要正確的清理,現在我們就來說一下如何清理。針對非託管資源 .NET 會採用一套標準的模式來完成清理工作。也就是說如果開發人員自己編寫的類中存在非託管資源,那麼這個類的使用者就會認爲這個類遵循 .NET 的垃圾清理模式。標準的 dispose 模式即實現了 IDisposable 接口,又實現了 finalizer ,這樣就可以在客戶端忘記調用 IDisposable.Dispose 的情況下也可以釋放資源。

Tip:在 .NET 中訪問非託管資源還可以通過 System.Runtime.Interop.SafeHandle 的派生類來訪問,該類正確實現了標準的 dispose 。

零、基類與子類需要注意

在詳細講解具體如何正確實現 dispose 模式前我們要了解基類與子類需要注意的內容。

  1. 基類
  • 實現 IDisposable 接口,以便釋放資源;
  • 如果類本身包含非託管資源,那麼就需要添加 finalizer 來防止客戶端忘記調用 Dispose 方法;
  • Dispose 方法和 finalizer 資源釋放必須交給虛方法,這樣子類纔可以重寫釋放資源的方法。
  1. 子類
  • 假如子類需要自己釋放資源,那就必須重寫基類所定義的釋放資源的虛方法,如果基類不存在這個虛方法那就不需要重寫;
  • 假如子類中存在使用非託管資源的情況,那就必須實現 finalizer ;
  • 重寫基類釋放資源的函數時,一定要調用基類的同名函數。

一、詳解

當我們編寫的類中存在必須釋放的資源的時候,我們就必須實現 IDisposable 接口,這個接口只包含一個無返回值的無參 Dispose 方法。在實現該方法時又如下幾個方面需要注意的:

  1. 釋放所有不再使用的非託管資源;
  2. 釋放所有不再使用的託管資源;
  3. 設置狀態標誌,表示對象已被清理過,如果有代碼調用被清理過的對象那麼就可以通過這個標誌得知,進而手動拋出 ObjectDisposedException 異常;
  4. 通過 GC.SupperssFinalize(this) 來阻止垃圾回收器重複清理已被清理過的對象。

雖然實現 Dispose 方法在保證釋放託管資源和非託管資源的情況下又能保證程序性能不會下降,但是它依然存在問題。子類在清理自身資源的同時還必須保證基類資源也被清理掉。這時大部分開發人員能想到的解決方法就是重寫 finalizer並調用基類釋放資源的方法,或者給 Dispos 方法添加新的邏輯並調用基類釋放資源的方法。這兩種方法都有類似的任務需要完成,因此這兩種方法包含了大量重複的代碼,這時我們就需要將這兩種方法中重複的代碼提取到一個 protected 級別的虛函數種,這樣基類只需寫好核心邏輯,子類重寫這個方法用來釋放自己的資源就可以了。一般我們會將這個方法定義成如下的樣子:

protected virtual void Dispose(bool isDisposing)

在上述代碼中如果 isDisposing 爲 true,則代表託管資源和非託管資源都要清理,如果爲 false 則代表只清理非託管資源。不管是 true 還是 false 我們都需要在子類中調用基類的 Dispose(bool) 方法。下面我們通過一段代碼來看一下我們該怎麼實現上述所說的內容。

public class DemoBase:IDisposable
{
    private bool baseDisposed = false;
    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool isDisposing)
    {
        if(baseDisposed)
        {
            return;
        }
        if(isDisposing)
        {
            //清理託管資源
        
        }
        //清理非託管資源

        baseDisposed=true;
    }
    public void Method()
    {
        if(baseDisposed)
        {
            throw new ObjectDisposedException("Demo","資源已被釋放!");
        }
        //more code
    }
}

public class Demo:DemoBase
{
    private bool disposed = false;
    protected override void Dispose(bool isDisposing)
    {
        if(baseDisposed)
        {
            return;
        }
        if(isDisposing)
        {
            //清理託管資源
        
        }
        //清理非託管資源
        
        base.Dispose(isDisposing);
        disposed=true;
    }
    //more code
}

上述代碼中 Demo 類繼承自 DemoBase,並重寫了 Dispose 方法。我們在代碼中看到基類和子類都使用了標誌狀態(baseDisposed,disposed),這裏爲什麼不使用同一個標誌狀態呢?主要是因爲子類在釋放資源的時候有可能會把標誌狀態改爲 true,這時在運行基類的 Dispose(bool) 方法基類會認爲資源已經釋放過了。

Tip:Dispose(bool) 和 finalizer 都必須編寫的很可靠,同時具備冪等性。也就是說無論多少次調用 Dispose(bool) 的效果都是一樣的。並且我們在釋放資源的時候我們不應該進行除了釋放資源以外的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章