C# 析構函數(Destructor)和終結器(Finalizer)——托管資源的釋放


本文內容

  • 使用析構函數釋放資源
  • Object.Finalize 方法
  • 資源的顯式釋放

 

使用析構函數釋放資源


析構函數用於析構類的實例。

  • 不能在結構中定義析構函數。只能對類使用析構函數。
  • 一個類只能有一個析構函數。
  • 無法繼承或重載析構函數。
  • 無法調用析構函數。它們是被自動調用的。
  • 析構函數既沒有修飾符,也沒有參數。

示例 1:類 Car 析構函數的聲明。

class Car
{
    /// <summary>
    /// 析構函數
    /// </summary>
    ~Car()
    {
        // cleanup statements...     
    }
}

該析構函數隱式調用對象基類的 Finalize 方法。因此,該析構函數被隱式地轉換為如下代碼:

protected override void Finalize()  
{  
    try
    {
        // Cleanup statements...   
    }     
    finally 
    {
        base.Finalize();  
    }
} 

這意味着,對繼承鏈中的所有實例遞歸調用 Finalize 方法。

說明:不要使用空的析構函數。如果類包含析構函數,則 Finalize  隊列中則會創建一個項。當調用析構函數時,將調用垃圾回收器(GC)來處理該隊列。如果析構函數為空,只會導致不必要的性能損失。

程序員無法控制何時調用析構函數,因為這由垃圾回收器決定。垃圾回收器檢查是否存在應用程序不再使用的對象。如果垃圾回收器認為某個對象符合析構,則調用析構函數(如果有的話),回收該對象的內存。程序退出時也會調用析構函數。

可以通過調用 Collect 強制進行垃圾回收,但大多數情況下應避免這樣做,因為會導致性能問題。

通常,.NET Framework 垃圾回收器會隱式地管理對象的內存分配和釋放。但當應用程序封裝窗口、文件

和網絡連接這類非托管資源時,應使用析構函數釋放這些資源。當對象符合析構時,垃圾回收器將運行對象的 Finalize 方法。

雖然垃圾回收器可以跟蹤封裝非托管資源的對象的生存期,但它不了解具體如何清理這些資源。常見的非托管源有:ApplicationContext、Brush、Component、ComponentDesigner、Container、Context、Cursor、FileStream、Font、Icon、Image、Matrix、Object、OdbcDataReader、OleDBDataReader、Pen、Regex、Socket、StreamWriter、Timer、Tooltip 等。

 

Object.Finalize 方法


允許 Object 在“垃圾回收”回收 Object 之前,嘗試釋放資源並執行其他清理操作。Finalize 是受保護的,因此只能通過此類或派生類訪問它。

對象變為不可訪問后,將自動調用此方法,除非已通過  GC.SuppressFinalize 調用使對象免除了終結。在應用程序域的關閉過程中,對沒有免除終結的對象將自動調用 Finalize,即使那些對象仍是可訪問的。對於給定的實例僅自動調用 Finalize 一次,除非使用  GC.ReRegisterForFinalize重新注冊該對象,並且后面沒有調用 GC.SuppressFinalize。

派生類型中的每個 Finalize 實現都必須調用其基類型的 Finalize 實現。這是唯一一種允許應用程序代碼調用 Finalize 的情況。

注意:C# 編譯器不允許你直接實現 Finalize 方法,因此 C# 析構函數自動調用其基類的析構函數。

Finalize 操作具有下列限制:

1)   垃圾回收過程中執行終結器的准確時間是不確定的。不保證資源在任何特定的時間都能釋放,除非調用 Close 方法或 Dispose 方法。

2)   即使一個對象引用另一個對象,也不能保證兩個對象的終結器以任何特定的順序運行。即,如果對象 A 具有對對象 B 的引用,並且兩者都有終結器,則當對象 A 的終結器啟動時,對象 B 可能已經終結了。

3)   運行終結器的線程是未指定的。

在下面的異常情況下,Finalize 方法可能不會運行完成或可能根本不運行:

1)   另一個終結器無限期地阻止(進入無限循環,試圖獲取永遠無法獲取的鎖,諸如此類)。由於運行時試圖運行終結器來完成,所以如果一個終結器無限期地阻止,則可能不會調用其他終結器。

2)   進程終止,但不給運行時提供清理的機會。在這種情況下,運行時的第一個進程終止通知是 DLL_PROCESS_DETACH 通知。

在關閉過程中,只有當可終結對象的數目繼續減少時,運行時才繼續 Finalize 對象。

如果 Finalize 或 Finalize 的重寫引發異常,並且運行庫並非寄宿在重寫默認策略的應用程序中,則運行庫將終止進程,並且不執行任何活動的 try-finally 塊或終結器。如果終結器無法釋放或銷毀資源,此行為可以確保進程完整性。

說明:默認情況下,Object.Finalize 不執行任何操作。只有在必要時才必須由派生類重寫它,因為如果必須運行 Finalize 操作,垃圾回收過程中的回收往往需要長得多的時間。如果 Object 保存了對任何資源的引用,則 Finalize 必須由派生類重寫,以便在垃圾回收過程中,在放棄 Object 之前釋放這些資源。當類型使用文件句柄或數據庫連接這類在回收使用托管對象時必須釋放的非托管資源時,該類型必須實現 Finalize。Finalize 可以采取任何操作,包括在垃圾回收過程中清理了對象后使對象復活(即,使對象再次可訪問)。但是,對象只能復活一次;在垃圾回收過程中,不能對復活對象調用 Finalize。

析構函數是執行清理操作的 C# 機制。析構函數提供了適當的保護措施,如自動調用基類型的析構函數。在 C# 代碼中,不能調用或重寫 Object.Finalize。

示例 2:當一個重構 Finalize 方法的對象被銷毀時,會調用 Finalize 方法。注意,在產品應用程序中,Finalize 方法會被重構,以便釋放對象擁有的非托管資源。另外,C# 提供析構函數,而沒有重構 Finalize 方法。

using System;
using System.Diagnostics;
 
public class ExampleClass
{
    Stopwatch sw;
    public ExampleClass()
    {
        sw = Stopwatch.StartNew();
        Console.WriteLine("Instantiated object");
    }
    public void ShowDuration()
    {
        Console.WriteLine("This instance of {0} has been in existence for {1}", this, sw.Elapsed);
    }
    ~ExampleClass()
    {
        Console.WriteLine("Finalizing object");
        sw.Stop();
        Console.WriteLine("This instance of {0} has been in existence for {1}", this, sw.Elapsed);
    }
}
public class Demo
{
    public static void Main()
    {
        ExampleClass ex = new ExampleClass();
        ex.ShowDuration();
    }
}

輸出結果如下:

Instantiated object    
This instance of ExampleClass has been in existence for 00:00:00.0011060
Finalizing object
This instance of ExampleClass has been in existence for 00:00:00.0036294

 

資源的顯式釋放


若應用程序在使用昂貴的外部資源,建議提供一種在垃圾回收器釋放對象前顯式地釋放資源的方式。可通過實現來自 IDisposable 接口的 Dispose 方法來完成這一點,該方法為對象執行必要的清理。這樣可大大提高應用程序的性能。即使有這種對資源的顯式控制,析構函數也是一種保護措施,可用來在對 Dispose 方法的調用失敗時清理資源。

更多信息,請參見 清理非托管資源

示例 3:創建三個類,這三個類構成了一個繼承鏈。這三個類都有析構函數。在 Main() 中,創建了派生程度最大的類的實例。程序運行時,這三個類的析構函數將自動被調用,並按照從派生程度最大的到派生程度最小的次序調用。

class First
{
    ~First()
    {
        System.Diagnostics.Trace.WriteLine("First's destructor is called.");
    }
}
class Second : First
{
    ~Second()
    {
        System.Diagnostics.Trace.WriteLine("Second's destructor is called.");
    }
}
class Third : Second
{
    ~Third()
    {
        System.Diagnostics.Trace.WriteLine("Third's destructor is called.");
    }
}
class TestDestructors
{
    static void Main()
    {
        Third t = new Third();
    }
}

在VS 輸出窗口(快捷鍵Ctrl+W,O)會看到如下信息:

……
Third's destructor is called.
Second's destructor is called.
First's destructor is called.
程序“[3796] TestDestructors.vshost.exe: 托管”已退出,返回值為0 (0x0)。

更多信息,請參見 C# 語言規范中的以下各章節:

a) 1.6.7.6 析構函數

b) 10.3.9.4 為析構函數保留的成員名稱

c)  10.13 析構函數(類)

d)  11.3.9 析構函數(結構)

C# 語言規范位於 Visual Studio 2008 安裝目錄下的 VC#/Specifications/1033/ 目錄。

 

參考


GC http://msdn.microsoft.com/zh-cn/library/0xy59wtx(v=VS.90).aspx

下載 Demo


免責聲明!

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



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