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方法一般是系統調用,用戶不去顯式的調用它)
    [Page]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.[Page]
 // 非托管資源
 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[Page]
   // 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;[Page]
   }
   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方法[Page]都是調用的是Dispose(bool disposing)方法,所以最終調用的是派生度最大的哪個函數,也就派生類中的Finalize和公共Dispose方法都是調用派生類自己的Dispose(bool disposing)方法。

 

 

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

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

 

來源:https://www.cnblogs.com/lzhdim/archive/2009/11/04/1595845.html

 

一:總結

 

Finalize自動釋放資源,Dispose()用於手動釋放資源。

 

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方法能夠被調用到,確保可以手動釋放任何需要釋放的資源。

 

二:實現 Finalize 和 Dispose 以清理非托管資源

http://msdn.microsoft.com/zh-cn/library/b1yfkh5e(VS.80).aspx

 

http://www.sudu.cn/info/index.php?op=article&id=12170

三:維護內部非托管資源的托管類的手段:Finalize()--終結和Dispose()--處置

非托管資源:原始的操作系統文件句柄,原始的非托管數據庫連接,非托管內存或其他非托管資源。

Finalize()特性:

  • 重寫Finalize()的唯一原因是,c#類通過PInvoke或復雜的COM互操作性任務使用了非托管資源(典型的情況是通過System.Runtime.InteropServices.Marshal類型定義的各成員)注:PInvoke是平台調用服務。
  • object中有finalize方法,但創建的類不能重寫此方法,若Overide會報錯,只能通過析構函數來達到同樣的效果。
  • Finalize方法的作用是保證.NET對象能在垃圾回收時清除非托管資源。
  • 在CLR在托管堆上分配對象時,運行庫自動確定該對象是否提供一個自定義的Finalize方法。如果是這樣,對象會被標記為可終結的,同時一個指向這個對象的指針被保存在名為終結隊列的內部隊列中。終結隊列是一個由垃圾回收器維護的表,它指向每一個在從堆上刪除之前必須被終結的對象。
  • 注意:Finalize雖然看似手動清除非托管資源,其實還是由垃圾回收器維護,它的最大作用是確保非托管資源一定被釋放。
  • 在結構上重寫Finalize是不合法的,因為結構是值類型,不在堆上,Finalize是垃圾回收器調用來清理托管堆的,而結構不在堆上。

Dispose()特性:

  • 為了更快更具操作性進行釋放,而非讓垃圾回收器(即不可預知)來進行,可以使用Dispose,即實現IDispose接口.
  • 結構和類類型都可以實現IDispose(與重寫Finalize不同,Finalize只適用於類類型),因為不是垃圾回收器來調用Dispose方法,而是對象本身釋放非托管資源,如Car.Dispose().如果編碼時沒有調用Dispose方法,以為着非托管資源永遠得不到釋放。
  • 如果對象支持IDisposable,總是要對任何直接創建的對象調用Dispose(),即有實現IDisposable接口的類對象都必須調用Dispose方法。應該認為,如果類設計者選擇支持Dispose方法,這個類型就需要執行清除工作。記住一點,如果類型實現了IDisposable接口,調用Dispose方法總是正確的。
  • .net基類庫中許多類型都實現IDisposable接口,並使用了Dispose的別名,其中一個別名如IO中的Close方法,等等別名。使得看起來更自然。
  • using關鍵字,實際內部也是實現IDisposable方法,用ildasm.exe查看使用了using的代碼的CIL,會發現是用try/finally去包含using中的代碼,並且在finally中調用dispose方法。

個人總結:

相同點:

  • 都是為了確保非托管資源得到釋放。

不同點:

  • finalize由垃圾回收器調用;dispose由對象調用。
  • finalize無需擔心因為沒有調用finalize而使非托管資源得不到釋放,而dispose必須手動調用。
  • finalize雖然無需擔心因為沒有調用finalize而使非托管資源得不到釋放,但因為由垃圾回收器管理,不能保證立即釋放非托管資源;而dispose一調用便釋放非托管資源。
  • 只有類類型才能重寫finalize,而結構不能;類和結構都能實現IDispose.原因請看Finalize()特性。

https://www.cnblogs.com/zoro-zero/p/13490336.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM