程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 析構函數和Dispose的使用區別,函數dispose區別

析構函數和Dispose的使用區別,函數dispose區別

編輯:C#入門知識

析構函數和Dispose的使用區別,函數dispose區別


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

 

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

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

下面是通過Reflector工具對上面代碼反射出來的結果,可以看出析構函數直接被翻譯成Finalize()函數了,因為Finalize函數不能被重寫,所以只能用析構函數的方式實現Finalize方法。

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

 

在.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。

 

然而,即使重復調用Finalize和Dispose也是不存在問題的,因為有變量_isDisposed的存在,資源只會被釋放一次,多余的調用會被忽略過去。因此,上面的模式保證了:

1、 Finalize只釋放非托管資源;

2、 Dispose釋放托管和非托管資源;

3、 重復調用Finalize和Dispose是沒有問題的;

4、 Finalize和Dispose共享相同的資源釋放策略,因此他們之間也是沒有沖突的。

 

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


Finalize

下面的規則概括了 Finalize 方法的使用准則:

 

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

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

       3、無法繼承或重載析構函數。

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

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

 

  • 僅在要求終結的對象上實現 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高手總結


1、Finalize方法(C#中是析構函數,以下稱析構函數)是用於釋放非托管資源的,而托管資源會由GC自動回收。所以,我們也可以這樣來區分 托管和非托管資源。所有會由GC自動回收的資源,就是托管的資源,而不能由GC自動回收的資源,就是非托管資源。在我們的類中直接使用非托管資源的情況很 少,所以基本上不用我們寫析構函數。

 

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

 

實現模型:

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

 

2、雖然可以手動釋放非托管資源,我們仍然要在析構函數中釋放非托管資源,這樣才是安全的應用程序。否則如果因為程序員的疏忽忘記了手動釋放非托管資源, 那麼就會帶來災難性的後果。所以說在析構函數中釋放非托管資源,是一種補救的措施,至少對於大多數類來說是如此。

 

3、由於析構函數的調用將導致GC對對象回收的效率降低,所以如果已經完成了析構函數該干的事情(例如釋放非托管資源),就應當使用SuppressFinalize方法告訴GC不需要再執行某個對象的析構函數。

 

4、析構函數中只能釋放非托管資源而不能對任何托管的對象/資源進行操作。因為你無法預測析構函數的運行時機,所以,當析構函數被執行的時候,也許你進行操作的托管資源已經被釋放了。這樣將導致嚴重的後果。

 

5、(這是一個規則)如果一個類擁有一個實現了IDispose接口類型的成員,並創建(注意是創建,而不是接收,必須是由類自己創建)它的實例對象,則 這個類也應該實現IDispose接口,並在Dispose方法中調用所有實現了IDispose接口的成員的Dispose方法。

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

 

 

———————————————————————————————————————— 

 一個人的時候,總是在想

    我的生活到底在期待什麼……

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved