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

來源:互聯網
上載者:User

在瞭解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中的“良民”。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.