析構函數和Dispose的使用區別

老生常談的問題了,MSDN也有很是詳細的說明但看起來不是很系統。也曾經作過度析,但沒有總結下來又忘了,此次整理一下MSDN和網上搜集的一些資料,以備不時只需。程序員

 

下面是MSDN對這兩個函數的建議使用方法數據庫

 1     // Design pattern for a base class.
 2     public class Base : IDisposable
 3     {
 4         //保證重複釋放資源時系統異常
 5         private bool _isDisposed = false;
 6 
 7         // 析構函數,編譯器自動生成Finalize()函數由GC自動調用,保證資源被回收。
 8         // 最好不要聲明空析構函數,形成性能問題
 9         // 若是沒有引用非託管資源就不須要顯示聲明析構函數,會形成性能問題,系統會自動生成默認析構函數
10         ~Base()
11         {
12             // 此處只須要釋放非託管代碼便可,由於GC調用時該對象資源可能還不須要釋放
13             Dispose(false);
14         }
15 
16         //外部手動調用或者在using中自動調用,同時釋放託管資源和非託管資源
17         public void Dispose()
18         {
19             Dispose(true);
20             GC.SuppressFinalize(this); ///告訴GC不須要再次調用
21         }
22 
23         protected virtual void Dispose(bool disposing)
24         {
25             if (!_isDisposed)
26             {
27                 if (disposing)
28                 {
29                     //釋放託管資源
30                 }
31                 // 釋放非託管資源
32                 // 釋放大對象
33 
34                 this._isDisposed = true;
35             }
36            
37         }
38 
39     }

 

下面是經過Reflector工具對上面代碼反射出來的結果,能夠看出析構函數直接被翻譯成Finalize()函數了,由於Finalize函數不能被重寫,因此只能用析構函數的方式實現Finalize方法。編程

 1 public class Base : IDisposable
 2 {
 3     // Fields
 4     private bool _isDisposed;
 5 
 6     // Methods
 7     public Base();
 8     public void Dispose();
 9     protected virtual void Dispose(bool disposing);
10     protected override void Finalize(); 
11 }

 

在.NET的對象中實際上有兩個用於釋放資源的函數:Dispose和Finalize。Finalize的目的是用於釋放非託管的資源,而Dispose是用於釋放全部資源,包括託管的和非託管的。安全

 

在這個模式中,void Dispose(bool disposing)函數經過一個disposing參數來區別當前是不是被Dispose()調用。若是是被Dispose()調用,那麼須要同時釋放 託管和非託管的資源。若是是被~Base()(也就是C#的Finalize())調用了,那麼只須要釋放非託管的資源便可。編程語言

 

這是由於,Dispose()函數是被其它代碼顯式調用並要求釋放資源的,而Finalize是被GC調用的。在GC調用的時候Base所引用的其它託管對象可能還不須要被銷燬,而且即便要銷燬,也會由GC來調用。所以在Finalize中只須要釋放非託管資源便可。另一方面,因爲在 Dispose()中已經釋放了託管和非託管的資源,所以在對象被GC回收時再次調用Finalize是沒有必要的,因此在Dispose()中調用 GC.SuppressFinalize(this)避免重複調用Finalize。ide

 

然而,即便重複調用Finalize和Dispose也是不存在問題的,由於有變量_isDisposed的存在,資源只會被釋放一次,多餘的調用會被忽略過去。所以,上面的模式保證了:函數

一、 Finalize只釋放非託管資源;工具

二、 Dispose釋放託管和非託管資源;性能

三、 重複調用Finalize和Dispose是沒有問題的;this

四、 Finalize和Dispose共享相同的資源釋放策略,所以他們之間也是沒有衝突的。

 

微軟對Dispose和Finalize方法使用準則


Finalize

下面的規則歸納了 Finalize 方法的使用準則:

 

       一、不能在結構中定義析構函數。只能對類使用析構函數。

       二、一個類只能有一個析構函數。

       三、沒法繼承或重載析構函數。

       四、沒法調用析構函數。它們是被自動調用的。

       五、析構函數既沒有修飾符,也沒有參數。

 

  • 僅在要求終結的對象上實現 Finalize。存在與 Finalize 方法相關的性能開銷。

 

  • 若是須要 Finalize 方法,應考慮實現 IDisposable,以使類的用戶能夠避免因調用 Finalize 方法而帶來的開銷。

 

  • 不要提升 Finalize 方法的可見性。該方法的可見性應該是 protected,而不是 public。

 

  • 對象的 Finalize 方法應該釋放該對象擁有的全部外部資源。此外,Finalize 方法應該僅釋放由該對象控制的資源。Finalize 方法不該該引用任何其餘對象。

 

  • 不要對不是對象的基類的對象直接調用 Finalize 方法。在 C# 編程語言中,這不是有效的操做。

 

  • 應在對象的 Finalize 方法中調用基類的 Finalize 方法。

    注意

    基類的 Finalize 方法經過 C# 和 C++ 析構函數語法自動進行調用。

 

釋放

下面的規則歸納了 Dispose 方法的使用準則:

 

  • 在封裝明確須要釋放的資源的類型上實現釋放設計方案。用戶能夠經過調用公共 Dispose 方法釋放外部資源。

 

  • 在一般包含控制資源的派生類型的基類型上實現釋放設計方案,即便基類型並不須要也如此。若是基類型有 Close 方法,這一般指示須要實現 Dispose。在這類狀況下,不要在基類型上實現 Finalize 方法。應該在任何引入須要清理的資源的派生類型中實現 Finalize。

 

  • 使用類型的 Dispose 方法釋放該類型所擁有的全部可釋放資源。

 

  • 對實例調用了 Dispose 後,應經過調用 GC.SuppressFinalize 方法禁止 Finalize 方法運行。此規則的一個例外是當必須用 Finalize 完成 Dispose 沒有完成的工做的狀況,但這種狀況不多見。

 

  • 若是基類實現了 IDisposable,則應調用基類的 Dispose 方法。

 

  • 不要假定 Dispose 將被調用。若是 Dispose 未被調用,也應該使用 Finalize 方法釋放類型所擁有的非託管資源。

 

  • 當資源已經釋放時,在該類型上從實例方法(非 Dispose)引起一個 ObjectDisposedException。該規則不適用於 Dispose 方法,該方法應該能夠在不引起異常的狀況下被屢次調用。

 

  • 經過基類型的層次結構傳播對 Dispose 的調用。Dispose 方法應釋放由此對象以及此對象所擁有的任何對象所控制的全部資源。例如,能夠建立一個相似 TextReader 的對象來控制 Stream 和 Encoding,二者均在用戶不知道的狀況下由 TextReader 建立。另外,Stream 和 Encoding 均可以獲取外部資源。當對 TextReader 調用 Dispose 方法時,TextReader 應繼而對 Stream 和 Encoding 調用 Dispose,使它們釋放其外部資源。

 

  • 考慮在調用了某對象的 Dispose 方法後禁止對該對象的使用。從新建立已釋放的對象是難以實現的方案。

 

  • 容許 Dispose 方法被調用屢次而不引起異常。此方法在首次調用後應該什麼也不作。

 

 

下面是CSDN高手總結


一、Finalize方法(C#中是析構函數,如下稱析構函數)是用於釋放非託管資源的,而託管資源會由GC自動回收。因此,咱們也能夠這樣來區分 託管和非託管資源。全部會由GC自動回收的資源,就是託管的資源,而不能由GC自動回收的資源,就是非託管資源。在咱們的類中直接使用非託管資源的狀況很 少,因此基本上不用咱們寫析構函數。

 

二、大部分的非託管資源會給系統帶來不少負面影響,例如數據庫鏈接不被釋放就可能致使鏈接池中的可用數據庫鏈接用盡。文件不關閉會致使其它進程沒法讀寫這個文件等等。

 

實現模型:

一、因爲大多數的非託管資源都要求能夠手動釋放,因此,咱們應該專門爲釋放非託管資源公開一個方法。實現IDispose接口的Dispose方法是最好的模型,由於C#支持using語句快,能夠在離開語句塊時自動調用Dispose方法。

 

二、雖然能夠手動釋放非託管資源,咱們仍然要在析構函數中釋放非託管資源,這樣纔是安全的應用程序。不然若是由於程序員的疏忽忘記了手動釋放非託管資源, 那麼就會帶來災難性的後果。因此說在析構函數中釋放非託管資源,是一種補救的措施,至少對於大多數類來講是如此。

 

三、因爲析構函數的調用將致使GC對對象回收的效率下降,因此若是已經完成了析構函數該乾的事情(例如釋放非託管資源),就應當使用SuppressFinalize方法告訴GC不須要再執行某個對象的析構函數。

 

四、析構函數中只能釋放非託管資源而不能對任何託管的對象/資源進行操做。由於你沒法預測析構函數的運行時機,因此,當析構函數被執行的時候,也許你進行操做的託管資源已經被釋放了。這樣將致使嚴重的後果。

 

五、(這是一個規則)若是一個類擁有一個實現了IDispose接口類型的成員,並建立(注意是建立,而不是接收,必須是由類本身建立)它的實例對象,則 這個類也應該實現IDispose接口,並在Dispose方法中調用全部實現了IDispose接口的成員的Dispose方法。

只有這樣的才能保證全部實現了IDispose接口的類的對象的Dispose方法可以被調用到,確保能夠手動釋聽任何須要釋放的資源。

相關文章
相關標籤/搜索