正確實現 IDisposable

來源:互聯網
上載者:User
 .NET中用於釋放對象資源的介面是IDisposable,但是這個介面的實現還是比較有講究的,此外還有Finalize和Close兩個函數。MSDN建議按照下面的模式實現IDisposable介面:.NET的對象中實際上有兩個用於釋放資源的函數:Dispose和Finalize。Finalize的目的是用於釋放非託管的資源,而Dispose是用於釋放所有資源,包括託管的和非託管的。 1 public class Foo: IDisposable
 2 {
 3     public void Dispose()
 4     {
 5        Dispose(true);
 6        GC.SuppressFinalize(this);
 7     }
 8 
 9     protected virtual void Dispose(bool disposing)
10     {
11        if (!m_disposed)
12        {
13            if (disposing)
14            {
15               // Release managed resources
16            }
17  
18            // Release unmanaged resources
19  
20            m_disposed = true;
21        }
22     }
23  
24     ~Foo()
25     {
26        Dispose(false);
27     }
28  
29     private bool m_disposed;
30 }
31  
32  在 在這個模式中,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進行了一次擴充,做法是引入了“!”函數,如下所示: 1 public ref class Foo
2 {
3 public:
4        Foo();
5        ~Foo();       // destructor
6        !Foo();       // finalizer
7 };
8  “!”函數(我實在不知道應該怎麼稱呼它)取代原來Managed C++中的Finalize()被GC調用。MSDN建議,為了減少代碼的重複,可以寫這樣的代碼:  1 ~Foo()
 2 {
 3     //釋放託管的資源
 4     this->!Foo();
 5 }
 6  
 7 !Foo()
 8 {
 9     //釋放非託管的資源
10 }
11  對於上面這個類,實際上C++/CLI產生對應的C#代碼是這樣的:  1 public class Foo
 2 {
 3     private void !Foo()
 4     {
 5        // 釋放非託管的資源
 6     }
 7  
 8     private void ~Foo()
 9     {
10        // 釋放託管的資源
11        !Foo();
12     }
13  
14     public Foo() 
15     {
16     }
17  
18     public void Dispose()
19     {
20        Dispose(true);
21        GC.SuppressFinalize(this);
22     }
23  
24     protected virtual void Dispose(bool disposing)
25     {
26        if (disposing)
27        {
28            ~Foo();
29        }
30        else
31        {
32            try
33            {
34               !Foo();
35            }
36            finally
37            {
38               base.Finalize();
39            }
40        }
41     }
42  
43     protected void Finalize()
44     {
45        Dispose(false);
46     }
47 }
48  由於~Foo()和!Foo()不會被重複調用(至少MS這樣認為),因此在這段代碼中沒有和前面m_disposed相同的變數,但是基本的結構是一樣的。 並且,可以看到實際上並不是~Foo()和!Foo()就是Dispose和Finalize,而是C++/CLI編譯器產生了兩個Dispose和Finalize函數,並在合適的時候調用它們。C++/CLI其實已經做了很多工作,但是唯一的一個問題就是依賴於使用者在~Foo()中調用!Foo()。 關於資源釋放,最後一點需要提的是Close函數。在語義上它和Dispose很類似,按照MSDN的說法,提供這個函數是為了讓使用者感覺舒服一點,因為對於某些對象,例如檔案,使用者更加習慣調用Close()。 然而,畢竟這兩個函數做的是同一件事情,因此MSDN建議的代碼就是: 
Dispose函數以獲得和Dispose相同的語義。這樣似乎就圓滿了,但是從另外一方面說,如果同時提供了Dispose和Close,會給使用者帶來一些困惑。沒有看到代碼細節的前提下,很難知道這兩個函數到底有什麼區別。因此在.NET的代碼設計規範中說,這兩個函數實際上只能讓使用者用一個。因此建議的模式是: 

1 public void Close()
2 {
3     Dispose(();
4 }

6 這裡直接調用不帶參數的 1 public class Foo: IDisposable
 2 {
 3     public void Close()
 4     {
 5        Dispose();
 6     }
 7  
 8     void IDisposable.Dispose()
 9     {
10        Dispose(true);
11        GC.SuppressFinalize(this);
12     }
13  
14     protected virtual void Dispose(bool disposing)
15     {
16        // 同前
17     }
18 }
19  這裡使用了一個所謂的介面顯式實現:void IDisposable.Dispose()。這個顯式實現只能通過介面來訪問,但是不能通過實作類別來訪問。因此:  1 Foo foo = new Foo();

3 foo.Dispose(); // 錯誤
4 (foo as IDisposable).Dispose(); // 正確
5 這樣做到了兼顧兩者。對於喜歡使用Close的人,可以直接用 foo.Close(),並且他看不到 Dispose()。對於喜歡Dispose的,他可以把類型轉換為 IDisposable 來調用,或者使用using語句。兩者皆大歡喜!

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.