IDisposable

.NET中用於釋放對象資源的接口是IDisposable,但是這個接口的實現還是比較有講究的,此外還有Finalize和Close兩個函式。
MSDN建議按照下面的模式實現IDisposable接口:
public class Foo: IDisposable{    public void Dispose()    {   Dispose(true);   GC.SuppressFinalize(this);   }   protected virtual void Dispose(bool disposing)    {   if (!m_disposed)   {if (disposing)  { // Release managed resources    }    // Release unmanaged resources m_disposed = true;   }    }   ~Foo()    {   Dispose(false);    }  private bool m_disposed; }
在.NET的對象中實際上有兩個用於釋放資源的函式:Dispose和Finalize。Finalize的目的是用於釋放非託管的資源,而Dispose是用於釋放所有資源,包括託管的和非託管的。
在這個模式中,void Dispose(bool disposing)函式通過一個disposing參數來區別當前是否是被Dispose()調用。如果是被Dispose()調用,那么需要同時釋放託管和非託管的資源。如果是被~Foo()(也就是C#的Finalize())調用了,那么只需要釋放非託管的資源即可。
這是因為,Dispose()函式是被其它代碼顯式調用並要求釋放資源的,而Finalize是被GC調用的。在GC調用的時候Foo所引用的其它託管對象可能還不需要被銷毀,並且即使要銷毀,也會由GC來調用。因此在Finalize中只需要釋放非託管資源即可。另外一方面,由於在Dispose()中已經釋放了託管和非託管的資源,因此在對象被GC回收時再次調用Finalize是沒有必要的,所以在Dispose()中調用GC.SuppressFinalize(this)避免重複調用Finalize。
然而,即使重複調用Finalize和Dispose也是不存在問題的,因為有變數m_disposed的存在,資源只會被釋放一次,多餘的調用會被忽略過去。
因此,上面的模式保證了:
1、 Finalize只釋放非託管資源;
2、 Dispose釋放託管和非託管資源;
3、 重複調用Finalize和Dispose是沒有問題的;
4、 Finalize和Dispose共享相同的資源釋放策略,因此他們之間也是沒有衝突的。
在C#中,這個模式需要顯式地實現,其中C#的~Foo()函式代表了Finalize()。而在C++/CLI中,這個模式是自動實現的,C++的類析構函式則是不一樣的。
按照C++語義,析構函式在超出作用域,或者delete的時候被調用。在Managed C++(即.NET 1.1中的託管C++)中,析構函式相當於CLR中的Finalize()方法,在垃圾收集的時候由GC調用,因此,調用的時機是不明確的。在.NET 2.0的C++/CLI中,析構函式的語義被修改為等價與Dispose()方法,這就隱含了兩件事情:
1、 所有的C++/CLI中的CLR類都實現了接口IDisposable,因此在C#中可以用using關鍵字來訪問這個類的實例。
2、 析構函式不再等價於Finalize()了。
對於第一點,這是一件好事,我認為在語義上Dispose()更加接近於C++析構函式。對於第二點,Microsoft進行了一次擴展,做法是引入了“!”函式,如下所示:
public ref class Foo{public:       Foo();       ~Foo();       // destructor       !Foo();       // finalizer};
“!”函式(我實在不知道應該怎么稱呼它)取代原來Managed C++中的Finalize()被GC調用。MSDN建議,為了減少代碼的重複,可以寫這樣的代碼:
~Foo(){    //釋放託管的資源    this->!Foo();}!Foo(){    //釋放非託管的資源}
對於上面這個類,實際上C++/CLI生成對應的C#代碼是這樣的:
public class Foo{    private void !Foo()    {       // 釋放非託管的資源    }    private void ~Foo()    {        // 釋放託管的資源        !Foo();     }     public Foo()     {     }     public void Dispose()     {        Dispose(true);        GC.SuppressFinalize(this);     }     protected virtual void Dispose(bool disposing)     {        if (disposing)        {            ~Foo();        }        else        {            try            {               !Foo();            }            finally            {               base.Finalize();            }        }     }     protected void Finalize()     {        Dispose(false);     } }
由於~Foo()和!Foo()不會被重複調用(至少MS這樣認為),因此在這段代碼中沒有和前面m_disposed相同的變數,但是基本的結構是一樣的。
並且,可以看到實際上並不是~Foo()和!Foo()就是Dispose和Finalize,而是C++/CLI編譯器生成了兩個Dispose和Finalize函式,並在合適的時候調用它們。C++/CLI其實已經做了很多工作,但是唯一的一個問題就是依賴於用戶在~Foo()中調用!Foo()。
關於資源釋放,最後一點需要提的是Close函式。在語義上它和Dispose很類似,按照MSDN的說法,提供這個函式是為了讓用戶感覺舒服一點,因為對於某些對象,例如檔案,用戶更加習慣調用Close()。
然而,畢竟這兩個函式做的是同一件事情,因此MSDN建議的代碼就是:
public void Close(){    Dispose(();}
這裡直接調用不帶參數的Dispose函式以獲得和Dispose相同的語義。這樣似乎就圓滿了,但是從另外一方面說,如果同時提供了Dispose和Close,會給用戶帶來一些困惑。沒有看到代碼細節的前提下,很難知道這兩個函式到底有什麼區別。因此在.NET的代碼設計規範中說,這兩個函式實際上只能讓用戶用一個。因此建議的模式是:
public class Foo: IDisposable{    public void Close()    {       Dispose();    }    void IDisposable.Dispose()    {        Dispose(true);        GC.SuppressFinalize(this);    }     protected virtual void Dispose(bool disposing)     {        // 同前     } }
這裡使用了一個所謂的接口顯式實現:void IDisposable.Dispose()。這個顯式實現只能通過接口來訪問,但是不能通過實現類來訪問。因此:
Foo foo = new Foo();foo.Dispose(); // 錯誤(foo as IDisposable).Dispose(); // 正確
這樣做到了兼顧兩者。對於喜歡使用Close的人,可以直接用 foo.Close(),並且他看不到 Dispose()。對於喜歡Dispose的,他可以把類型轉換為 IDisposable 來調用,或者使用using語句。兩者皆大歡喜!

相關詞條

熱門詞條

聯絡我們