析構器

析構器 (finalizer),計算機語言中的析構函式,當一個對象在消亡的時候,由編譯器自動調用。

析構器
由於.NET平台的自動垃圾收集機制,C#語言中類的析構器不再如傳統C++那么必要,析構器不再承擔對象成員的記憶體釋放--自動垃圾收集機制保證記憶體的回收。實際上C#中已根本沒有delete操作!析構器只負責回收處理那些非系統的資源,比較典型的如:打開的檔案,獲取的視窗句柄,資料庫連線,網路連線等等需要用戶自己動手釋放的非記憶體資源。我們看下面例子的輸出:
using System;
class MyClass1
{
~MyClass1()
{
Console.WriteLine("MyClass1's destructor");
}
}
class MyClass2: MyClass1
{
~MyClass2()
{
Console.WriteLine("MyClass2's destructor");
}
}
public class Test
{
public static void Main()
{
MyClass2 MyObject = new MyClass2();
MyObject = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
編譯程式並運行可以得到下面的輸出:
MyClass2's destructor
MyClass1's destructor
其中程式中最後兩句是保證類的析構器得到調用。GC.Collect()是強迫通用語言運行時進行啟動垃圾收集執行緒進行回收工作。而GC.WaitForPendingfinalizers()是掛起目前的執行緒等待整個終止化(Finalizaion)操作的完成。終止化(Finalizaion)操作保證類的析構器被執行,這在下面會詳細說明。
析構器不會被繼承,也就是說類內必須明確的聲明析構器,該類才存在析構器。用戶實現析構器時,編譯器自動添加調用父類的析構器,這在下面的Finalize方法中會詳細說明。析構器由於垃圾收集機制會被在合適的的時候自動調用,用戶不能自己調用析構器。只有實例析構器,而沒有靜態析構器。
那么析構器是怎么被自動調用的?這在 .Net垃圾回收機制由一種稱作終止化(Finalizaion)的操作來支持。.Net系統預設的終止化操作不做任何操作,如果用戶需要釋放非受管資源,用戶只要在析構器內實現這樣的操作即可--這也是C#推薦的做法。我們看下面這段代碼:
using System;
class MyClass1
{
~MyClass1()
{
Console.WritleLine("MyClass1 Destructor");
}
}
而實際上,從生成的中間代碼來看我們可以發現,這些代碼被轉化成了下面的代碼:
using System;
class MyClass1
{
protected override void Finalize()
{
try
{
Console.WritleLine("My Class1 Destructor");
}
finally
{
base.Finalize();
}
}
}
實際上C#編譯器不允許用戶自己重載或調用Finalize方法--編譯器徹底禁止了父類的Finalize方法(由於C#的單根繼承性質,System.Object類是所有類的祖先類,自然每個類都有Finalize方法),好像這樣的方法根本不存在似的。我們看下面的代碼實際上是錯的:
using System;
class MyClass
{
override protected void Finalize() {}// 錯誤
public void MyMethod()
{
this.Finalize();// 錯誤
}
}
但下面的代碼卻是正確的:
using System;
class MyClass
{
public void Finalize()
{
Console.WriteLine("My Class Destructor");
}
}
public class Test
{
public static void Main()
{
MyClass MyObject=new MyClass();
MyObject.Finalize();
}
}
實際上這裡的Finalize方法已經徹底脫離了“終止化操作”的語義,而成為C#語言的一個一般方法了。值得注意的是這也禁止了父類System.Object的Finalize方法,所以要格外小心!
終止化操作在.Net運行時里有很多限制,往往不被推薦實現。當對一個對象實現了終止器(Finalizer)後,運行時便會將這個對象的引用加入一個稱作終止化對象引用集的佇列,作為要求終止化的標誌。當垃圾收集開始時,若一個對象不再被引用但它被加入了終止化對象引用集的佇列,那么運行時並不立即對此對象進行垃圾收集工作,而是將此對象標誌為要求終止化操作對象。待垃圾收集完成後,終止化執行緒便會被運行時喚醒執行終止化操作。顯然這之後要從終止化對象引用集的鍊表中將之刪去。而只有到下一次的垃圾收集時,這個對象才開始真正的垃圾收集,該對象的記憶體資源才被真正回收。容易看出來,終止化操作使垃圾收集進行了兩次,這會給系統帶來不小的額外開銷。終止化是通過啟用執行緒機制來實現的,這有一個執行緒安全的問題。.Net運行時不能保證終止化執行的順序,也就是說如果對象A有一個指向對象B的引用,兩個對象都有終止化操作,但對象A在終止化操作時並不一定有有效的對象A引用。.Net運行時不允許用戶在程式運行中直接調用Finalize()方法。如果用戶迫切需要這樣的操作,可以實現IDisposable接口來提供公共的Dispose()方法。需要說明的是提供了Dispose()方法後,依然需要提供Finalize方法的操作,即實現假託的析構函式。因為Dispose()方法並不能保證被調用。所以.Net運行時不推薦對對象進行終止化操作即提供析構函式,只是在有非受管資源如資料庫的連線,檔案的打開等需要嚴格釋放時,才需要這樣做。
大多數時候,垃圾收集應該交由.Net運行時來控制,但有些時候,可能需要人為地控制一下垃圾回收操作。例如在操作了一次大規模的對象集合後,我們確信不再在這些對象上進行任何的操作了,那我們可以強制垃圾回收立即執行,這通過調用System.GC.Collect() 方法即可實現,但頻繁的收集會顯著地降低系統的性能。還有一種情況,已經將一個對象放到了終止化對象引用集的鏈上了,但如果我們在程式中某些地方已經做了終止化的操作,即明確調用了Dispose()方法,在那之後便可以通過調用System.GC.SupressFinalize()來將對象的引用從終止化對象引用集鏈上摘掉,以忽略終止化操作。終止化操作的系統負擔是很重的。
在深入了解了.NET運行時的自動垃圾收集功能後,我們便會領會C#中的析構器為什麼繞了這么大的彎來實現我們的編程需求,才能把記憶體資源和非記憶體資源的回收做的遊刃有餘--這也正是析構的本原!

相關詞條

相關搜尋

熱門詞條

聯絡我們