複製鏈接
請複製以下鏈接發送給好友

析構器

鎖定
析構器 (finalizer),計算機語言中的析構函數,當一個對象在消亡的時候,由編譯器自動調用。
中文名
析構器
外文名
finalizer)
類    型
計算機語言
缺    點
在.Net運行時裏有很多限制

目錄

析構器簡介

由於.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#中的析構器為什麼繞了這麼大的彎來實現我們的編程需求,才能把內存資源和非內存資源的回收做的遊刃有餘--這也正是析構的本原!