C#中的解構函式

pamxy發表於2013-11-10

轉自:http://www.cnblogs.com/paper/archive/2009/07/31/1535998.html

解構函式 

解構函式(destructor) 與建構函式相反,當物件脫離其作用域時(例如物件所在的函式已呼叫完畢),系統自動執行解構函式。解構函式往往用來做“清理善後” 的工作(例如在建立物件時用new開闢了一片記憶體空間,應在退出前在解構函式中用delete釋放)。


以C++語言為例,解構函式名也應與類名相同,只是在函式名前面加一個波浪符~,例如~stud( ),以區別於建構函式。它不能帶任何引數,也沒有返回值(包括void型別)。只能有一個解構函式,不能過載。如果使用者沒有編寫解構函式,編譯系統會自動生成一個預設的解構函式,它也不進行任何操作。所以許多簡單的類中沒有用顯式的解構函式。

解構器

     我們知道,‘解構器’被用來清除類的事例。當我們在C#中使用解構器是,我們必須記住以下幾點:

       一個類只能有一個解構器。 
       解構器不能被繼承或過載。 
       解構器不能被呼叫。他們是自動被(編譯器)呼叫的。 
       解構器不能帶修飾或引數。 
   下面是類MyClass解構器的一個宣告:

 

~ Class()   
{  
    // Cleaning up code goes here  
}

 

     程式設計師不能控制解構器何時將被執行因為這是由垃圾收集器決定的。垃圾收集器檢查不在被應用程式使用的物件。它認為這些條件是符合清楚的並且收回它們的記憶體。解構器也在程式退出時被呼叫。當解構器執行時其背後所發生的那一幕是解構器隱式呼叫物件基類的Object.Finalize方法。因此上述解構器程式碼被隱含轉化成:

 

protected override void Finalize()  
{  
    try  
    {  
       // Cleaning up .  
    }  
    finally  
    {  
       base.Finalize();  
    }
}

     現在,讓我們看一個解構器怎樣被呼叫的例子。我們有三個類A,B和C 。B派生自A,C派生自B。每個類有它們自己的構造器和解構。在類App的main函式中,我們建立C的物件。

 

using System;  
class A  
{  
public A()  
{  
   Console.WriteLine("Creating A");  
}  
~A()  
{  
   Console.WriteLine("Destroying A");  
}  
}  
   
class B:A  
{  
public B()  
{  
   Console.WriteLine("Creating B");  
}  
~B()  
{  
   Console.WriteLine("Destroying B");  
}  
   
}  
class C:B  
{  
public C()  
{  
   Console.WriteLine("Creating C");  
}  
   
~C()  
{  
   Console.WriteLine("Destroying C");  
}  
}  
class App  
{  
public static void Main()  
{  
   C c=new C();  
   Console.WriteLine("Object Created ");  
   Console.WriteLine("Press enter to Destroy it");  
   Console.ReadLine();  
   c=null;  
   //GC.Collect();  
   Console.Read();  
}  
}
  
     正如我們預料的,基類的構造器將會被執行並且程式會等待使用者按‘enter’。當這個發生,我們把類C的物件置為null.但解構器沒有被執行..!!??正像我們所說的,程式設計師無法控制解構器何時被執行因為這是由垃圾蒐集器決定的。但程式退出時解構器被呼叫了。你能通過重定向程式的o/p到文字檔案來檢查這個。我將它輸出在這裡。注意到基類的解構器被呼叫了,因為在背後base.Finalize()被呼叫了。

 

Creating A
Creating B
Creating C
Object Created 
Press enter to Destroy it
Destroying C
Destroying B
Destroying A
 
     所以,如果一旦你使用完物件你就想呼叫解構器,你該怎麼做?有兩個方法:

呼叫垃圾蒐集器來清理。

實現IDisposable的Dispose方法。

 

呼叫垃圾蒐集器

 

     你能通過呼叫GC.Collect方法強制垃圾蒐集器來清理記憶體,但在大多數情況下,這應該避免因為它會導致效能問題。在上面的程式中,在GC.Collect()處移除註釋。編譯並執行它。現在,你能看到解構器在控制檯中被執行了。

 

實現IDisposable介面

 

     IDisposable 介面包括僅有的一個公共方法,其宣告為void Dispose()。我們能實現這個方法來關閉或釋放非託管資源如實現了這個介面的類事例所控制的檔案,流,和控制程式碼等。這個方法被用做所有任務聯合物件的資源釋放。當實現了這個方法,物件必須尋求確保所有擁有的資源被繼承結構中關聯的資源也釋放(不能把握,翻不出來)。

 

class MyClass:IDisposable  
{  
     public void Dispose()  
{  
   //implementation  
}  
}

 

     當我們實現了IDisposable介面時,我們需要規則來確保Dispose被適當地呼叫。

  

聯合使用解構器和IDisposable介面

 

Public class MyClass:IDisposable  
{  
private bool IsDisposed=false;  
public void Dispose()  
{  
   Dispose(true);  
   GC.SupressFinalize(this);  
}  
protected void Dispose(bool Diposing)  
{  
   if(!IsDisposed)  
   {  
   if(Disposing)  
   {  
    //Clean Up managed resources  
   }  
   //Clean up unmanaged resources  
}  
IsDisposed=true;  
}  
~MyClass()  
{  
   Dispose(false);  
}  
}

     在這裡過載了Dispose(bool)來做清理工作,並且所有的清理程式碼都僅寫在這個方法中。這個方法被解構器和IDisposable.Dispose()兩著呼叫。我們應該注意Dispose(bool)沒有在任何地方被呼叫除了在IDisposable.Dispose()和解構器中。

     當一個客戶呼叫IDisposable.Dispose()時,客戶特意地想要清理託管的和非託管資源,並且因此完成清理工作。有一件你必須注意的事情是我們在清理資源之後立即呼叫了GC.SupressFinalize(this)。這個方法通知垃圾蒐集器不需要呼叫解構器,因為我們已經做了清理。

     注意上面的例子,解構器使用引數false呼叫Dispose。這裡,我們確信垃圾蒐集器蒐集了託管資源。我們僅僅做非託管資源的清理。

 

結論

 

     儘管如此我們花費一些時間實現IDisposable介面,如果客戶不能合適地呼叫它們會怎樣?為此C#有一個酷的解決方案。‘using’程式碼塊。它看起來像這樣:

using (MyClass objCls =new MyClass())  
{  
   
}
 
     當控制從using塊通過成功執行到結束或者丟擲異常退出時,MyClass的IDispose.Dispose()將會被執行。記住你例示的物件必須實現System.IDisposable介面。using語句定義了哪個物件將被清除的一個範圍。

注:
   建構函式與解構函式的區別:
      建構函式和解構函式是在類體中說明的兩種特殊的成員函式。
      建構函式的功能是在建立物件時,使用給定的值來將物件初始化。
      解構函式的功能是用來釋放一個物件的。在物件刪除前,用它來做一些清理工作,它與建構函式的功能正好相反。


相關文章