對.Net 垃圾回收Finalize 和Dispose的理解

我們先來談談析構函數。

析構函數是不可繼承的。因此,除了自已所聲明的析構函數外,一個類不具有其他析構函數。

由於析構函數要求不能帶有參數,因此它不能被重載,所以一個類至多只能有一個析構函數。

析構函數是自動調用的,它不能被顯式調用。當任何代碼都不再可能使用一個實例時,該實例就符合被銷燬的條件。此後,它所對應的實例析構函數隨時均可能被調用。銷燬一個實例時,按照從派生程度最大到派生程度最小的順序,調用該實例的繼承鏈中的各個析構函數。析構函數可以在任何線程上執行。

下列示例的輸出

using System;
class A
{
~A() {
Console.WriteLine("A's destructor");
}
}
class B: A
{
~B() {
Console.WriteLine("B's destructor");
}
}
class Test
{
static void Main() {
B b = new B();
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}

B's destructor
A's destructor

這是由於繼承鏈中的析構函數是按照從派生程度最大到派生程度最小的順序調用的。

析構函數實際上是重寫了 System.Object 中的虛方法 Finalize。C# 程序中不允許重寫此方法或直接調用它(或它的重寫)。例如,下列程序

class A
{
override protected void Finalize() {}   // error
public void F() {
this.Finalize();                     // error
}
}

包含兩個錯誤。

編譯器的行爲就像此方法和它的重寫根本不存在一樣。因此,以下程序:

class A
{
void Finalize() {}                     // permitted
}

是有效的,所聲明的方法隱藏了 System.ObjectFinalize 方法。


好,現在我們開始來談談Finalize 和Dispose。

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

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

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

  1. Finalize是CLR提供的一個機制, 它保證如果一個類實現了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
{
  //前面我們說了析構函數實際上是重寫了 System.Object 中的虛方法 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(),提高了非託管資源的使用效率並提升了系統性能

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