C#資源釋放

轉自:http://www.cnblogs.com/psunny/archive/2009/07/07/1518812.htmlhtml

深入理解C#中資源釋放

      今天個人一個朋友看到我寫的那篇《C#中用AJAX驗證用戶登陸》時,給我指出了點小毛病。就是在用戶登陸時,若是用戶登陸失敗,在下面這段代碼中,都會new出來一個User對象,若是連續登陸失敗屢次,就會生成多個User對象,而它們在登陸失敗後已經無用了,依然佔據着內存,就算是C#有垃圾回收機制,但不肯定何時對這些對象進行回收。
而後去網上找了一篇C#資源釋放的文章,講的很透徹,和你們分享一下。程序員

Code

 

      首先,咱們須要明確2個概念。算法

 

第一個就是不少人用.Net寫程序,會談到託管這個概念。那麼.Net所指的資源託管究竟是什麼意思,是相對於全部資源,仍是隻限於某一方面資源?不少人對此不是很瞭解,其實.Net所指的託管只是針對內存這一個方面,並非對於全部的資源;所以對於Stream,數據庫的鏈接,GDI+的相關對象,還有Com對象等等,這些資源並非受到.Net管理而統稱爲非託管資源。而對於內存的釋放和回收,系統提供了GC-Garbage Collector,而至於其餘資源則須要手動進行釋放。
 
那麼第二個概念就是什麼是垃圾,經過我之前的文章,會了解到.Net類型分爲兩大類,一個就是值類型,另外一個就是引用類型。前者是分配在棧上,並不須要GC回收;後者是分配在堆上,所以它的內存釋放和回收須要經過GC來完成。GC的全稱爲「Garbage Collector」,顧名思義就是垃圾回收器,那麼只有被稱爲垃圾的對象才能被GC回收。也就是說,一個引用類型對象所佔用的內存須要被GC回收,須要先成爲垃圾。那麼.Net如何斷定一個引用類型對象是垃圾呢,.Net的判斷很簡單,只要斷定此對象或者其包含的子對象沒有任何引用是有效的,那麼系統就認爲它是垃圾。
 
明確了這兩個基本概念,接下來講說GC的運做方式以及其的功能。內存的釋放和回收須要伴隨着程序的運行,所以系統爲GC安排了獨立的線程。那麼GC的工做大體是,查詢內存中對象是否成爲垃圾,而後對垃圾進行釋放和回收。那麼對於GC對於內存回收採起了必定的優先算法進行輪循回收內存資源。其次,對於內存中的垃圾分爲兩種,一種是須要調用對象的析構函數,另外一種是不須要調用的。GC對於前者的回收須要經過兩步完成,第一步是調用對象的析構函數,第二步是回收內存,可是要注意這兩步不是在GC一次輪循完成,即須要兩次輪循;相對於後者,則只是回收內存而已。
 
很明顯得知,對於某個具體的資源,沒法確切知道,對象析構函數何時被調用,以及GC何時會去釋放和回收它所佔用的內存。那麼對於從C、C++之類語言轉換過來的程序員來講,這裏須要轉變觀念。
 
那麼對於程序資源來講,咱們應該作些什麼,以及如何去作,才能使程序效率最高,同時佔用資源能儘快的釋放。前面也說了,資源分爲兩種,託管的內存資源,這是不須要咱們操心的,系統已經爲咱們進行管理了;那麼對於非託管的資源,這裏再重申一下,就是Stream,數據庫的鏈接,GDI+的相關對象,還有Com對象等等這些資源,須要咱們手動去釋放。
 
如何去釋放,應該把這些操做放到哪裏比較好呢。.Net提供了三種方法,也是最多見的三種,大體以下:
<!--[if !supportLists]-->1. <!--[endif]-->析構函數;
<!--[if !supportLists]-->2. <!--[endif]-->繼承IDisposable接口,實現Dispose方法;
<!--[if !supportLists]-->3. <!--[endif]-->提供Close方法。
 
通過前面的介紹,能夠知道析構函數只能被GC來調用的,那麼沒法肯定它何時被調用,所以用它做爲資源的釋放並非很合理,由於資源釋放不及時;可是爲了防止資源泄漏,畢竟它會被GC調用,所以析構函數能夠做爲一個補救方法。而Close與Dispose這兩種方法的區別在於,調用完了對象的Close方法後,此對象有可能被從新進行使用;而Dispose方法來講,此對象所佔有的資源須要被標記爲無用了,也就是此對象被銷燬了,不能再被使用。例如,常見SqlConnection這個類,當調用完Close方法後,能夠經過Open從新打開數據庫鏈接,當完全不用這個對象了就能夠調用Dispose方法來標記此對象無用,等待GC回收。明白了這兩種方法的意思後,你們在往本身的類中添加的接口時候,不要歪曲了這二者意思。數據庫

接下來講說這三個函數的調用時機,我用幾個試驗結果來進行說明,可能會使你們的印象更深。
首先是這三種方法的實現,大體以下:編程

Code

      對於Close來講不屬於真正意義上的釋放,除了注意它須要顯示被調用外,我在此對它很少說了。而對於析構函數而言,不是在對象離開做用域後馬上被執行,只有在關閉進程或者調用GC.Collect方法的時候才被調用,參看以下的代碼運行結果。函數

Code

運行的結果爲:
After created!
Destructor called!post

 顯然在出了Create函數外,myClass對象的析構函數沒有被馬上調用,而是等顯示調用GC.Collect才被調用。
 
對於Dispose來講,也須要顯示的調用,可是對於繼承了IDisposable的類型對象可使用using這個關鍵字,這樣對象的Dispose方法在出了using範圍後會被自動調用。例如:測試

     using ( DisposeClass myClass  =   new  DisposeClass() )
    
{
        
//other operation here
    }

如上運行的結果以下:
Dispose called!this

       那麼對於如上DisposeClass類型的Dispose實現來講,事實上GC還須要調用對象的析構函數,按照前面的GC流程來講,GC對於須要調用析構函數的對象來講,至少通過兩個步驟,即首先調用對象的析構函數,其次回收內存。也就是說,按照上面所寫的Dispose函數,雖然說被執行了,可是GC仍是須要執行析構函數,那麼一個完整的Dispose函數,應該經過調用GC.SuppressFinalize(this )來告訴GC,讓它不用再調用對象的析構函數中。那麼改寫後的DisposeClass以下:url

Code

      經過以下的代碼進行測試。

Code

運行的結果以下:
Dispose called!
After Run!

 

顯然對象的析構函數沒有被調用。經過如上的實驗以及文字說明,你們會獲得以下的一個對比表格。
 
析構函數
Dispose 方法
Close 方法
意義
銷燬對象
銷燬對象
關閉對象資源
調用方式
不能被顯示調用,會被GC 調用
須要顯示調用
或者經過using 語句
須要顯示調用
調用時機
不肯定
肯定,在顯示調用或者離開using 程序塊
肯定,在顯示調用時
 
那麼在定義一個類型的時候,是否必定要給出這三個函數地實現呢。
 
個人建議大體以下。
<!--[if !supportLists]--> 1.<!--[endif]--> 提供析構函數,避免資源未被釋放,主要是指非內存資源;
<!--[if !supportLists]--> 2.<!--[endif]--> 對於Dispose Close 方法來講,須要看所定義的類型所使用的資源(參看前面所說),而決定是否去定義這兩個函數;
<!--[if !supportLists]--> 3.<!--[endif]--> 在實現Dispose 方法的時候,必定要加上「 GC.SuppressFinalize( this ) 」語句,避免再讓GC 調用對象的析構函數。
 
C#程序所使用的內存是受託管的,但不意味着濫用,好地編程習慣有利於提升代碼的質量以及程序的運行效率。