C#中的非託管資源釋放(Finalize&Dispose)

 在瞭解Finalize和Dispose之前,我們需要了解兩個概念,一個是託管資源,一個非委託資源。
a.其中託管資源一般是指被CLR控制的內存資源,這些資源的管理可以由CLR來控制,例如程序中分配的對象,作用域內的變量等。
b.而非託管資源是CLR不能控制或者管理的部分,這些資源有很多,比如文件流,數據庫的連接,系統的窗口句柄,打印機資源等等……這些資源一般情況下不存在於Heap(內存中用於存儲對象實例的地方)中。
.Net平臺中,CLR爲程序員提供了一種很好的內存管理機制,使得程序員在編寫代碼時不需要顯式的去釋放自己使用的內存資源(這些在先前C和C++中是需要程序員自己去顯式的釋放的)。這種管理機制稱爲GC(garbage collection)。GC的作用是很明顯的,當系統內存資源匱乏時,它就會被激發,然後自動的去釋放那些沒有被使用的託管資源(也就是程序員沒有顯式釋放的對象)。
但正如上面說的,CLR的GC功能也只能釋放託管資源,對於非託管資源例如窗口,文件和網絡連接等,它都只能跟蹤非託管資源的生存期,而不知道如何去釋放它。這樣就會出現當資源用盡時就不能提供資源能夠提供的服務,windows的運行速度就會變慢。這樣的情況會出現在數據庫的連接當中,當你沒有顯式的釋放一個數據庫資源時,如果還是不斷的申請數據庫資源,那麼到一定時候程序就會拋出一個異常。
所以,當我們在類中封裝了對非託管資源的操作時,我們就需要顯式,或者是隱式的釋放這些資源。而上面提到的Finalize和Dispose方法分別就是隱式和顯式操作中分別使用到的方法。
Finalize一般情況下用於基類不帶close方法或者不帶Dispose顯式方法的類,也就是說,在Finalize過程中我們需要隱式的去實現非託管資源的釋放,然後系統會在Finalize過程完成後,自己的去釋放託管資源。
如果要實現Dispose方法,可以通過實現IDisposable接口,這樣用戶在使用這個類的同時就可以顯示的執行Dispose方法,釋放資源。

以下是MSDN上提出的Finalize和Dispose方法的使用指南,如果你的類遵循這個標準的話,你寫出的類在.Net平臺上就是一個“良民”。

Finalize
下面的規則概括了 Finalize 方法的使用指南。

1.僅在要求終結的對象上實現 Finalize。存在與 Finalize 方法相關的性能開銷。
如果需要 Finalize 方法,應考慮實現 IDisposable,以使類的用戶可以避免調用 Finalize 方法帶來的開銷。(juky_huang注:在實現IDisposable的類中,可以通過GC.SuppressFinalize來停止Finalize的運行,這樣只要顯式的調用了Dispose方法,就能給用戶提供更小的開銷。如果用戶沒有顯式的調用Dispose方法,也就是沒有停止Finalize的運行,這樣就可以隱式的實現非託管資源的釋放)
2.不要使 Finalize 方法更可見。它應該是 protected,而不是 public。 (juky_huang注:這個很重要,Finalize方法一般是系統調用,用戶不去顯式的調用它)
3.對象的 Finalize 方法應該釋放對象擁有的任何外部資源。此外,Finalize 方法應該僅釋放由對象控制的資源。Finalize 方法不應該引用任何其他對象。
4.不要對不是對象的基類的對象直接調用 Finalize 方法。在 C# 編程語言中,這不是有效的操作。
5.從對象的 Finalize 方法調用 base.Finalize 方法。(juky_huang注:就是派生類調用基類的Finalize方法)
注意   基類的 Finalize 方法由 C# 和 C++ 的託管擴展的析構函數語法自動調用。


Dispose
下面的規則概括了 Dispose 方法的使用指南:

1.在封裝明確需要釋放的資源的類型上實現處置設計方案。用戶可以通過調用公共 Dispose 方法釋放外部資源。
2.在通常包含控制資源的派生類型的基類型上實現處置設計方案,即使基類型並不需要。如果基類型有 close 方法,這通常指示需要實現 Dispose。在這類情況下,不要在基類型上實現 Finalize 方法。應該在任何引入需要清理的資源的派生類型中實現 Finalize。
3.使用類型的 Dispose 方法釋放類型所擁有的任何可處置資源。
4.對實例調用了 Dispose 後,禁止 Finalize 方法通過調用 GC.SuppressFinalize 方法運行。此規則的例外情況是當必須用 Finalize 完成 Dispose 沒有覆蓋的工作時,但這種情況很少見。
5.如果基類實現 IDisposable,則調用基類的 Dispose 方法。
6.不要假定 Dispose 將被調用。如果 Dispose 未被調用,也應該使用 Finalize 方法釋放類型所擁有的非託管資源。
7.處置了資源之後,在該類型(非 Dispose)上從實例方法引發一個 ObjectDisposedException。該規則不適用於 Dispose 方法,因爲在不引發異常的情況下,該方法應該可以被多次調用。
8.通過基類型的層次結構將調用傳播到 Dispose。Dispose 方法應釋放此對象控制的所有資源和此對象所擁有的任何對象。例如,可以創建一個類似 TextReader 的對象來控制 Stream 和 Encoding,兩者均在用戶不知道的情況下由 TextReader 創建。另外,Stream 和 Encoding 都可以獲取外部資源。當對 TextReader 調用Dispose 方法時,它應該依次對 Stream 和 Encoding 調用 Dispose,使它們釋放它們的外部資源。
9.應考慮在調用了對象的 Dispose 方法後不允許使用對象。重新創建已處置的對象是難以實現的方案。
10.允許 Dispose 方法被調用多次而不引發異常。此方法在首次調用後應該什麼也不做。

有了以上的基礎後,我們看一段代碼,這段代碼是Dispose的一個實現,這個代碼如果仔細的去考慮的話,非常的有趣,在這裏我們又會看到C#中一個非常常用的技術,多態性,如果你看過我在前面寫的一篇關於虛擬方法的文章的話,你可以從中理解下面代碼的精要之處。

public class BaseResource: IDisposable
{
 // Pointer to an external unmanaged resource.
 // 非託管資源
 private IntPtr handle;
 // Other managed resource this class uses.
 // 託管資源
 private Component Components;
 // Track whether Dispose has been called.
 // 是否已經釋放資源的標誌
 private bool disposed = false;

 // Constructor for the BaseResource object.
 public BaseResource()
 {
  // Insert appropriate constructor code here.
 }

 // Implement IDisposable.
 // Do not make this method virtual.
 // A derived class should not be able to override this method.
 // 提供給外部用戶顯示調用的方法,實際操作是在類的帶參數的虛函數Dispose(bool disposing)中實現
 public void Dispose()
 {
  // 表示用戶顯示調用
  Dispose(true);
  // Take yourself off the Finalization queue
  // to prevent finalization code for this object
  // from executing a second time.
  // 由於用戶是顯示調用,所以資源釋放不再由GC來完成
  GC.SuppressFinalize(this);
 }

 // Dispose(bool disposing) executes in two distinct scenarios.
 // If disposing equals true, the method has been called directly
 // or indirectly by a user's code. Managed and unmanaged resources
 // can be disposed.
 // If disposing equals false, the method has been called by the
 // runtime from inside the finalizer and you should not reference
 // other objects. Only unmanaged resources can be disposed.
 protected virtual void Dispose(bool disposing)
 {
  // Check to see if Dispose has already been called.
  // 如果已經釋放,不做再次的操作,出現在用戶多次調用的情況下
  if(!this.disposed)
  {
   // If disposing equals true, dispose all managed
   // and unmanaged resources.
   if(disposing)
   {
    // Dispose managed resources.
    // 用戶是顯示調用的話,我們就要手工的操作託管資源
    Components.Dispose();
   }
   // Release unmanaged resources. If disposing is false,
   // only the following code is executed.
   CloseHandle(handle);
   handle = IntPtr.Zero;
   // Note that this is not thread safe.
   // Another thread could start disposing the object
   // after the managed resources are disposed,
   // but before the disposed flag is set to true.
   // If thread safety is necessary, it must be
   // implemented by the client.

  }
  disposed = true;        
 }

 // Use C# destructor syntax for finalization code.
 // This destructor will run only if the Dispose method
 // does not get called.
 // It gives your base class the opportunity to finalize.
 // Do not provide destructors in types derived from this class.
 // 析構函數
 ~BaseResource()     
 {
  // Do not re-create Dispose clean-up code here.
  // Calling Dispose(false) is optimal in terms of
  // readability and maintainability.
  // 表示本次調用是隱式調用,由Finalize方法調用,即託管資源釋放由GC來完成
  Dispose(false);
 }

 // Allow your Dispose method to be called multiple times,
 // but throw an exception if the object has been disposed.
 // Whenever you do something with this class,
 // check to see if it has been disposed.
 public void DoSomething()
 {
  if(this.disposed)
  {
   throw new ObjectDisposedException();
  }
 }
}

// Design pattern for a derived class.
// Note that this derived class inherently implements the
// IDisposable interface because it is implemented in the base class.
public class MyResourceWrapper: BaseResource
{
 // A managed resource that you add in this derived class.
 private ManagedResource addedManaged;
 // A native unmanaged resource that you add in this derived class.
 private NativeResource addedNative;
 private bool disposed = false;

 // Constructor for this object.
 public MyResourceWrapper()
 {
  // Insert appropriate constructor code here.
 }
  // 重寫Dispose方法,釋放派生類自己的資源,並且調用基類的Dispose方法
 protected override void Dispose(bool disposing)
 {
  if(!this.disposed)
  {
   try
   {
    if(disposing)
    {
     // Release the managed resources you added in
     // this derived class here.
     addedManaged.Dispose();        
    }
    // Release the native unmanaged resources you added
    // in this derived class here.
    CloseHandle(addedNative);
    this.disposed = true;
   }
   finally
   {
    // Call Dispose on your base class.
    base.Dispose(disposing);
   }
  }
 }
}
// 在這裏,派生類沒有實現~MyResourceWrapper和public Dispose方法,應爲他們已經繼承了基類的這些特性,這也是我說本示例代碼精要之處,他使用到了多態性原理,下面我會簡單分析
// This derived class does not have a Finalize method
// or a Dispose method without parameters because it inherits
// them from the base class.


本示例中有兩個類一個是基類BaseResource,一個是派生類MyResourceWrapper,首先我們必須理解一下幾點:
1.類型的 Dispose 方法應該釋放它擁有的所有資源。它還應該通過調用其父類型的 Dispose 方法釋放其基類型擁有的所有資源。該父類型的Dispose 方法應該釋放它擁有的所有資源並同樣也調用其父類型的 Dispose 方法,從而在整個基類型層次結構中傳播該模式。
2.如果顯式的調用了Dispose方法,我們就在Dispose方法中實現託管資源和非託管資源的釋放,使用 GC.SuppressFinalize 方法來停止Finalize方法。因爲如果用戶調用了Dispose方法,那麼我們就不必隱式的完成資源的釋放,應爲Finalizes會大大的減損性能。(Finalize一般只用於用戶沒有顯式的調用Dispose方法,需要我們隱式完成時才使用)
3.要確保始終正確地清理資源,Dispose 方法應該可以被多次調用而不引發任何異常

示例中最主要的一個方法就是帶參數的Dispose方法,本例中所有的具體操作都是放到這裏來做的,它是一個受保護的虛函數,可以被派生類重寫,並且如果派生類自己有對非託管資源的調用,那麼派生類就要按照上面提到的要求,首先釋放自己的資源,然後調用base.Dispose來實現基類的資源釋放。(juky_huang注:這就是我們所謂的傳播特性)
帶參數的Dispose方法通過所帶的參數disposing來判斷,本次的Dispose操作是由Finalize發起還是由用戶顯式的調用公共Dispose方法發起的。如果爲true則表示由公共的Dispose方法發起,如果爲false表示是在GC調用Finalize方法時候發起。所以當爲true時,我們就需要釋放託管資源和非託管資源,並且禁止GC的Finalize操作,因爲用戶可以直接通過顯示調用來減小性能開銷。如果爲false時,表示我們只需要釋放非託管資源,因爲本次調用是由GC的Finalize引起的,所以託管資源的釋放可以讓GC來完成。
示例中還有一個值得注意的地方,就是在多次顯示調用Dispose時,如果資源已經處置,那麼我們就要忽略本次操作,而不拋出異常。這個特性由disposed來決定。
好了,現在我們來看看這個程序的一個精要之處,那就是在派生類中,沒有公共的Dispose方法,和Finalize方法(就是析構函數),那如果我們調用派生類對象時,是怎麼實現資源釋放的呢,剛開始我也不是很瞭解,後來仔細一看,突然發現其實很簡單,它使用到了類的多態性來完成。
因爲在派生類中使用了方法重寫,所以在派生類中的Dispose(bool disposing)方法的派生度最大。由於基類中的Finalize和公共Dispose方法都是調用的是Dispose(bool disposing)方法,所以最終調用的是派生度最大的哪個函數,也就派生類中的Finalize和公共Dispose方法都是調用派生類自己的Dispose(bool disposing)方法。對於虛擬方法,可以參看我寫的一篇文章地址是:

http://blog.csdn.net/juky_huang/archive/2005/10/26/517069.aspx

例如,現在我們有一個派生類實例A,如果我們顯示調用A.Dispose()方法,它會去調用基礎中的public Dispose方法這是由於繼承的原因,在public Dispose方法中調用的又是Dispose(bool disposing)方法,由於這個方法已經被重寫,所以它實際調用的不是基類中的Dispose(bool disposing)方法,而是A自己的Dispose(bool disposing)方法。這是根據運行時類型來定的。所以最終還是實現了,先調用A中的資源釋放,然後才調用base.Dispose方法來完成基類的資源釋放。
如果用戶沒有顯示調用Dispose方法,那麼Finalize方法就會有效,過程和上面是類似的。

從上面可以看出,對於非託管資源的釋放,有一個很好的規則,只要我們按照這個規則來做,你寫的代碼就是.Net中的“良民”。

在寫這篇文章時,我參照了很多MSDN的資料,如果有興趣,可以去看看

轉自 : http://blog.csdn.net/juky_huang/archive/2005/10/28/518309.aspx

外一篇: 

對.Net 垃圾回收的C#編程相關方面(Finalize 和Dispose(bool disposing)和 Dispose())的一些理解體會

Finalize 和Dispose(bool disposing)和 Dispose() 的相同點:

這三者都是爲了釋放非託管資源服務的.

Finalize 和 Dispose() 和Dispose(bool disposing)的不同點:

1.      Finalize是CRL提供的一個機制, 它保證如果一個類實現了Finalize方法,那麼當該類對象被垃圾回收時,垃圾回收器會調用Finalize方法.而該類的開發者就必須在Finalize方法中處理非託管資源的釋放. 但是什麼時候會調用Finalize由垃圾回收器決定,該類對象的使用者(客戶)無法控制.從而無法及時釋放掉寶貴的非託管資源.由於非託管資源是比較寶貴了,所以這樣會降低性能.

2.      Dispose(bool disposing)不是CRL提供的一個機制, 而僅僅是一個設計模式(作爲一個IDisposable接口的方法),它的目的是讓供類對象的使用者(客戶)在使用完類對象後,可以及時手動調用非託管資源的釋放,無需等到該類對象被垃圾回收那個時間點.這樣類的開發者就只需把原先寫在Finalize的釋放非託管資源的代碼,移植到Dispose(bool disposing)中.  而在Finalize中只要簡單的調用 "Dispose(false)"(爲什麼傳遞false後面解釋)就可以了.

這個時候我們可能比較疑惑,爲什麼還需要一個Dispose()方法?難道只有一個Dispose(bool disposing)或者只有一個Dispose()不可以嗎?
答案是: 
        只有一個Dispose()不可以. 爲什麼呢?因爲如果只有一個Dispose()而沒有Dispose(bool disposing)方法.那麼在處理實現非託管資源釋放的代碼中無法判斷該方法是客戶調用的還是垃圾回收器通過Finalize調用的.無法實現判斷如果是客戶手動調用,那麼就不希望垃圾回收器再調用Finalize()(調用GC.SupperFinalize方法).另一個可能的原因(:我們知道如果是垃圾回收器通過Finalize調用的,那麼在釋放代碼中我們可能還會引用其他一些託管對象,而此時這些託管對象可能已經被垃圾回收了, 這樣會導致無法預知的執行結果(千萬不要在Finalize中引用其他的託管對象).

        所以確實需要一個bool disposing參數, 但是如果只有一個Dispose(bool disposing),那麼對於客戶來說,就有一個很滑稽要求,Dispose(false)已經被Finalize使用了,必須要求客戶以Dispose(true)方式調用,但是誰又能保證客戶不會以Dispose(false)方式調用呢?所以這裏採用了一中設計模式:重載 把Dispose(bool disposing)實現爲 protected, 而Dispose()實現爲Public,那麼這樣就保證了客戶只能調用Dispose()(內部調用Dispose(true)//說明是客戶的直接調用),客戶無法調用Dispose(bool disposing).

範例如下:

public class BaseResource: IDisposable
{
  //析構函數自動生成 Finalize 方法和對基類的 Finalize 方法的調用.默認情況下,一個類是沒有析構函數的,也就是說,對象被垃圾回收時不會被調用Finalize方法
  ~BaseResource()     
   {
      // 爲了保持代碼的可讀性性和可維護性,千萬不要在這裏寫釋放非託管資源的代碼
      // 必須以Dispose(false)方式調用,以false告訴Dispose(bool disposing)函數是從垃圾回收器在調用Finalize時調用的
      Dispose(false);
   }
  
  
   // 無法被客戶直接調用
   // 如果 disposing 是 true, 那麼這個方法是被客戶直接調用的,那麼託管的,和非託管的資源都可以釋放
   // 如果 disposing 是 false, 那麼函數是從垃圾回收器在調用Finalize時調用的,此時不應當引用其他託管對象所以,只能釋放非託管資源
   protected virtual void Dispose(bool disposing)
   {
     
         // 那麼這個方法是被客戶直接調用的,那麼託管的,和非託管的資源都可以釋放
         if(disposing)
         {
            // 釋放託管資源
            OtherManagedObject.Dispose();
         }
        
        
         //釋放非託管資源
         DoUnManagedObjectDispose();
        
                
         // 那麼這個方法是被客戶直接調用的,告訴垃圾回收器從Finalization隊列中清除自己,從而阻止垃圾回收器調用Finalize方法.        
         if(disposing) 
           GC.SuppressFinalize(this);  
          
   } 
  
   //可以被客戶直接調用
   public void Dispose()
   {
     //必須以Dispose(true)方式調用,以true告訴Dispose(bool disposing)函數是被客戶直接調用的
      Dispose(true);     
   }
}

上面的範例達到的目的:

1/ 如果客戶沒有調用Dispose(),未能及時釋放託管和非託管資源,那麼在垃圾回收時,還有機會執行Finalize(),釋放非託管資源,但是造成了非託管資源的未及時釋放的空閒浪費

2/ 如果客戶調用了Dispose(),就能及時釋放了託管和非託管資源,那麼該對象被垃圾回收時,不回執行Finalize(),提高了非託管資源的使用效率並提升了系統性能


可以參考SqlConnection對象的New, Open, Close(內部調用Dispose())的使用經歷可以加深對他們的理解.謝謝!
轉自: http://www.cnblogs.com/liushouzhao/archive/2008/04/05/1137896.html


我的理解:

1, 值類型不需要GC回收,因爲值類型變量存放在棧中,隨着作用域的結束而被釋放。不存在回收問題。

2,引用類型需要GC回收,也可以自己回收。

 

發佈了94 篇原創文章 · 獲贊 2 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章