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