C# 使用(employs)自動內(nèi)存管理(automatic memory management),這解放了開發(fā)人員必須手動分配與釋放對象內(nèi)存戰(zhàn)勇的麻煩。自動內(nèi)存管理策略由垃圾回收器(garbage collector)實現(xiàn)。對象的內(nèi)存管理生命周期如下:
垃圾回收器維護著對象使用(object usage)的信息,透過這些信息為內(nèi)存管理作出決策,諸如何處內(nèi)存存放了新建對象,何時遷移(relocate)一個對象以及一個對象何時開始不被使用或不可訪問。
和其它有垃圾回收器的語言類似,C# 的設(shè)計也在努力使垃圾回收器能實現(xiàn)更廣泛的(wide range)內(nèi)存管理策略(memory management policies)比方說,C# 并不強求析構(gòu)函數(shù)一定要運行,并不強求對象一滿足條件就要立即被回收,并不強求析構(gòu)函數(shù)一定要按照某個特定順序執(zhí)行或是在某個特定線程上執(zhí)行。
垃圾回收器的行為是可控的,在某種程度上講,可以通過 System.GC
上的靜態(tài)方法(來實現(xiàn))。通過這個類可以請求執(zhí)行一次回收操作、運行(或不運行)析構(gòu)函數(shù),等。
由于垃圾回收器在決定「何時回收(collect)對象并執(zhí)行析構(gòu)函數(shù)」這一點上充分自由(allowed wide latitude),因此一個合格的實現(xiàn)也許會產(chǎn)生與下面所示代碼有所不同的輸出。在程序
using System;
class A
{
~A() {
Console.WriteLine("Destruct instance of A");
}
}
class B
{
object Ref;
public B(object o) {
Ref = o;
}
~B() {
Console.WriteLine("Destruct instance of B");
}
}
class Test
{
static void Main() {
B b = new B(new A());
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
中,創(chuàng)建了 A 和 B 的實例。當(dāng) null 值被賦予變量 b 之后,這些資源成為符合回收條件,這是由于自此之后用戶所寫的任何代碼都不可能訪問到它們。輸出的結(jié)果可能是
Destruct instance of A
Destruct instance of B
或者
Destruct instance of B
Destruct instance of A
,這是由于語言并未對對象被垃圾回收的順序強加限制。
盡管很相近,但區(qū)別「符合銷毀條件(eligible for destruction)」與「符合回收條件(eligible for collection)」還是比較重要的,比方說:
using System;
class A
{
~A() {
Console.WriteLine("Destruct instance of A");
}
public void F() {
Console.WriteLine("A.F");
Test.RefA = this;
}
}
class B
{
public A Ref;
~B() {
Console.WriteLine("Destruct instance of B");
Ref.F();
}
}
class Test
{
public static A RefA;
public static B RefB;
static void Main() {
RefB = new B();
RefA = new A();
RefB.Ref = RefA;
RefB = null;
RefA = null;
// A and B now eligible for destruction
GC.Collect();
GC.WaitForPendingFinalizers();
// B now eligible for collection, but A is not
if (RefA != null)
Console.WriteLine("RefA is not null");
}
}
在上面程序中,如果立即回收器(garbage collector)選擇在運行 B 的析構(gòu)函數(shù)前先運行 A 的析構(gòu)函數(shù),那么這個程序也許會輸出:
Destruct instance of A
Destruct instance of B
A.F
RefA is not null
注意,盡管 A 的實例未被使用,依舊調(diào)用了 A 的析構(gòu)函數(shù),同時 A 的方法也有可能被其它析構(gòu)函數(shù)調(diào)用(此例中為 F)。同時還要注意,析構(gòu)函數(shù)的運行可能導(dǎo)致對象在主程序中再次變的可訪問。在此例中,B 析構(gòu)函數(shù)的運行導(dǎo)致了先前并未使用的 A 實例在通過引用 Test.RefA
時變得再次可訪問。在調(diào)用 WaitForPendingFinalizers
之后,B 的實例便符合回收條件了,但由于引用了 Test.RefA
,所以實例 A 還不能被回收。
為了避免混淆和意外行為,建議類的析構(gòu)函數(shù)僅對自己內(nèi)部的字段數(shù)據(jù)進行清理,不要去干涉其它所引用的對象或靜態(tài)字段。
另一個替代析構(gòu)函數(shù)的方法是讓類實現(xiàn) System.IDisposable
接口。這將允許對象的客戶端(client of the object)決定何時釋放自身資源,通常會在 using
語句(第八章第十三節(jié))通過資源的方式訪問該對象。
更多建議: