析構方法:
我們知道引用類型都有構造方法(constructor),相對應的也有一個析構方法(destructor).顧名思義,構造方法,就是在創建這個對象時,要執行的方法。例如,我們可以通過構造方法,
初始化字段。析構方法,就是當這個對象被垃圾回收後(garbage collected,我們稱回收對象內存為垃圾回收 garbage collection),要執行的方法。關於析構方法,需要大家注意的是,垃圾回收一個對象,並不是析構方法完成的(下面會講到垃圾回收的工作原理),析構方法只有在對象被垃圾回收後才執行。也就是說,析構方法對於一個對象來講,不是必須的。很多時候,如果加上它,反而不好(下面講garbage collector怎樣工作時,就會明白不好的原因)。
既然垃圾回收不歸析構方法負責,那麼它有什麼用呢?因為垃圾回收是CLR自動執行的,CLR只能處理受管理資源(managed resource),那些不受管理資源(unmanaged resource)就需要我們
自己去處理了。例如文件讀取(file stream),當對象結束時,我們需要把文件流關掉。關掉文件流的代碼,就要在析構方法中。也就是說,析構方法的用處,在處理不受管理資源時用處比較大。
看個例子:
class FileProcessor { FileStream file=null;
public FileProcess(string fileName) { this.file=file.OpenRead(fileName); //open file for reading } ~FileProcess() //析構方法與構造方法很相似,不同的是析構方法要加一個~ { this.file.Close(); //close file } }
file對象,是CRL垃圾回收,當file被垃圾回收後,運行析構方法FileProcess,此時我們將非管理資源關掉。this.file.Close().
這裡有幾條對析構方法的限制:
1.只有引用類型才可以有析構方法。
struct MyStruct(){ ~MyStruct(){....}//結構是值類型,所以不能有析構方法 }
2.不能對析構方法提供訪問修飾符.
public ~FileProcessor(){};//錯誤的
3.析構方法不可以有參數.
~FileProcessor(int param) { ....// //錯誤的 };
之所以會有這三條限制,是因為析構方法只能由CLR調用,自己不可以調用。因為,你不知道引用對象什麼時候被垃圾回收了,只有對象被垃圾回收了,程序才會自己調用析構方法。
在內部,程序會將我們寫的析構方法,轉換一下。例如:
class FileProcessor { ~fileProcessor(){...} //析構方法 } class FileProcessor { protected override void Finalize() { try{ } finally{ base.Finalize(); //CLR會將析構方法轉變成這個 } } }
我們把執行析構方法的過程,稱為結束(finalization,或終結。)
垃圾回收機制(garbage collector)
我們上邊提到,回收內存空間,回收不用的引用類型對象的過程稱為垃圾回收(garbage collection).這個過程是由CLR通過Garbage collector這樣一個機制去運行的。
當我們在程序中創建變量,會在內存中開辟一段空間。電腦的內存不是無限大的,我們需要在變量超越定義的范圍(程序不再需要這個變量了)時,對它所占的內存進行管理,處理這些內存。當變量不再被使用時,需要把內存回收。值類型變量回收內存,非常簡單。
當它超出定義的范圍時就會自動被毀掉,被占的內存也會自動回收。超出定義的范圍,指的是當它不再被使用,不能再被使用。引用類型變量回收內存,比較麻煩。例如:
fileProcessor myFp=new fileProcessor(); fileProcessor referenceToMyFp=myFp;
想一下這種情況,myFp對象已經超出定義的范圍。此時我們去回收內存,要把myFp引用堆上的內存回收。可是,恰恰此時,referenceToMyFp還在引用准備回收的內存,如果此時把內存回收,當程序運行referenceToMyFp時,程序就會出錯。所以,只用當所有引用對象都超出定義的范圍時,也就是都不再使用時,才可以去回收這些對象引用的內存。確保程序中這些指向同一塊兒的引用對象全部不再使用,是很困難,很復雜的.所以C#設計者,把處理引用類型回收內存的工作,交給了CLR(Common language running).CLR利用garbage collector機制,來處理這些事情。
垃圾回收機制工作原理
garbage collector在自己的線程中工作,在特定的時間執行。一般,當程序運行到一個方法的最後時,就會工作。它工作時,其他線程就會暫時停止工作。因為,garbage collector可能會移除或者更新對象引用。
1.garbage collector會創建一張表,表裡存放所有的可得到對象(reachable objects,.可得到對象,說白了就是指那些還在使用,不能回收內存的對象。)。
2.檢查一下那些不可得到對象(unreachable objects,就是那些超出定義范圍,需要回收內存的對象),看看他們是否有析構方法。(destructor),如果有,就把這些對象放入一個叫做
freachable queue的隊列裡。
3.把那些不可得到對象,且沒有析構方法的對象所指向的內存地址回收。它是通過將那些可得對象在堆上的地址下移,這樣堆上面就留出了可用的內存。此時,garbage collector也會更新堆
上的引用地址。(因為,地址有變化)。
4.此時,程序中其他的線程恢復工作。
5.garbage collector 通過調用自己的Finalize方法來結束不可得到對象,且有析構方法的對象。(前面我們講了,析構方法不是必須的,有時候會給程序帶來復雜,累贅。如果,沒有析構方法,當
CLR運行Garbage collector時,第五步就可以省去)。
資源管理
有些資源很稀缺,稀缺到不能等到CLR去調用析構方法去處理。例如database connections,file handles.此時,我們就需要寫一個dispose方法,手動去處理資源。(dispose可以換成任何名
字,這裡只是舉個例子)。例如:
TextReader reader=new StreamReader(filename); string line; while((line=reader.ReadLine())!=null) { Console.WriteLine(line); } reader.Close();
這裡,Close就是一個dispose方法,手動去關掉文件流。但是,這樣有個問題,當出現異常時,有可能導致reader.Close()不被執行,這樣資源一直被占用。所以,我們要改寫成:
TextReader reader=new StreamReader(filename); try { string line; while((line=reader.ReaderLine())!=null) { Console.WriteLine(line); } } finally { reader.Close(); }
這樣無論如何,reader.Close()都會被執行,資源都會被釋放。不過即便改寫成這樣,也不是最完美的。因為,當我們執行完finally塊兒內的代碼,也就是在try finally之後,我們有可能
無意中使用reader對象,就是釋放掉資源後的reader對象。此時,我們可以用using 語句來解決這些不足。我們將上面的代碼改成using之後:
using(TextReader reader=new StreamReader(filename)) { string line; while((line=reader.ReadLine())!=null) { Console.Writle(line); } }
執行完using之後,資源自動釋放,越過using范圍,對象不能用。一個對象如果要被支持使用using,該對象必須實現IDispose接口。
我們自己創建一個類,繼承IDispose接口,讓它可以用在USING語句中。這裡我們應該區分一下析構方法,與dispose方法。我們知道析構方法一定會執行,但是不知道什麼時候執行。我們
知道dispose方法什麼時候執行,但是不知道它會不會執行,因為類裡有dispose方法,不代表一定會把這個類用在using語句中。此時,我們就可以通過析構方法來調用dispose方法,這樣可
以保證dispose方法一定被調用。