c#中的對象分為實值型別和參考型別,二者最大的區別在於資料的儲存方式和儲存位置.WINDOWS作業系統使用虛擬定址系統來管理程式運行時產生的資料存放.簡單的說,該系統管理著一個記憶體地區,在該地區中劃撥出一部分出來專門存放實值型別變數,稱為堆棧,堆棧採用先進後出的原則,將實值型別變數從地區的最高地址位開始向低位地址儲存,先進後出,後進先出的管理方式保證了實值型別變數在出了範圍後能即使的清除佔用的記憶體地區,由於堆棧速度快,所儲存的資料一般不太大,這部分一般不需要使用者專門操作. 實值型別儲存在堆棧匯總, 堆棧有非常高的效能,但對於所有的變數來說還是不太靈活。通常我們希望使用一個方法分配記憶體,來儲存一些資料,並在方法退出後的很長一段時間內資料仍是可以使用的。只要是用new運算子來請求儲存空間,就存在這種可能性——例如所有的參考型別。此時就要使用託管堆。它在垃圾收集器的控制下工作,託管堆(或簡稱為堆)是系統管理的大記憶體地區中的另一個記憶體地區。要瞭解堆的工作原理和如何為引用資料類型分配記憶體,看看下面的代碼:
Customer arabel = new Customer();
這行程式碼完成了以下操作:首先,分配堆上的記憶體,以儲存Customer執行個體(一個真正的執行個體,不只是一個地址)。然後把變數arabel的值設定為分配給新Customer對象的記憶體位址(它還調用合適的Customer()建構函式初始化類執行個體中的欄位,但我們不必擔心這部分)。
Customer執行個體沒有放在堆棧中,而是放在記憶體的堆中。如果我們這樣操作:
Customer newaddress = arabel ;
這時候,newaddress也會儲存在堆棧中,其值和arabel 相同,都是儲存Customer執行個體的堆地址.
知道了這些,我們會發現這樣一個問題,如果堆棧中arabel 和newaddress兩個變數到期銷毀,那堆中儲存的Customer對象會怎樣?實際上它仍保留在堆中,一直到程式停止,或垃圾收集器刪除它為止. C#的垃圾收集器如果沒有顯示調用,會定時運行並檢查記憶體,刪除沒有任何變數引用的資料.看起來似乎不錯,但是想想,記憶體回收行程並不是時時檢查,它是定時運行,而在這段時間內如果產生大量的到期資料駐留在記憶體中..... 那麼或許我們可以通過調用System.GC.Collect(),強迫垃圾收集器在代碼的某個地方運行,System.GC是一個表示垃圾收集器的.NET基類, Collect()方法則調用垃圾收集器。但是,這種方式適用的場合很少,(難道銷毀一個對象就讓記憶體回收檢查一便記憶體嗎?)例如,代碼中有大量的對象剛剛停止引用,就適合調用垃圾收集器。況且垃圾收集器的邏輯不能保證在一次垃圾收集過程中,從堆中刪除所有到期資料,對於不受記憶體回收行程管理的未託管對象(例如檔案控制代碼、網路連接和資料庫連接),它是無能為力的。那該怎麼做呢?
這時需要制定專門的規則,確保未託管的資源在回收類的一個執行個體時釋放。
在定義一個類時,可以使用兩種機制來自動釋放未託管的資源。這些機制常常放在一起實現,因為每個機制都為問題提供了略為不同的解決方案。這兩個機制是:
● 聲明一個解構函式,作為類的一個成員
● 在類中實現System.IDisposable介面
下面依次討論這兩個機制,然後介紹如何同時實現它們,以獲得最佳的效果。
解構函式
前面介紹了建構函式可以指定必須在建立類的執行個體時進行的某些操作,在垃圾收集器刪除對象時,也可以調用解構函式。由於執行這個操作,所以解構函式初看起來似乎是放置釋放未託管資源、執行一般清理操作的代碼的最佳地方。但是,事情並不是如此簡單。由於垃圾回首器的運行規則決定了,不能在解構函式中放置需要在某一時刻啟動並執行代碼,如果對象佔用了寶貴而重要的資源,應儘可能快地釋放這些資源,此時就不能等待垃圾收集器來釋放了.
IDisposable介面
一個推薦替代解構函式的方式是使用System.IDisposable介面。IDisposable介面定義了一個模式(具有語言級的支援),為釋放未託管的資源提供了確定的機制,並避免產生解構函式固有的與垃圾函數器相關的問題。IDisposable介面聲明了一個方法Dispose(),它不帶參數,返回void,Myclass的方法Dispose()的執行代碼如下:
class Myclass : IDisposable{ public void Dispose() { // implementation }}
Dispose()的執行代碼顯式釋放由對象直接使用的所有未託管資源,並在所有實現IDisposable介面的封裝對象上調用Dispose()。這樣,Dispose()方法在釋放未託管資源時提供了精確的控制。
假定有一個類ResourceGobbler,它使用某些外部資源,且執行IDisposable介面。如果要執行個體化這個類的執行個體,使用它,然後釋放它,就可以使用下面的代碼:
ResourceGobbler theInstance = new ResourceGobbler(); // 這裡是theInstance 對象的使用過程 theInstance.Dispose();
如果在處理過程中出現異常,這段代碼就沒有釋放theInstance使用的資源,所以應使用try塊,編寫下面的代碼:
ResourceGobbler theInstance = null;try{ theInstance = new ResourceGobbler();// 這裡是theInstance 對象的使用過程}finally { if (theInstance != null) theInstance.Dispose();}
即使在處理過程中出現了異常,這個版本也可以確保總是在theInstance上調用Dispose(),總是釋放由theInstance使用的資源。但是,如果總是要重複這樣的結構,代碼就很容易被混淆。C#提供了一種文法,可以確保在引用超出範圍時,在對象上自動調用Dispose()(但不是Close())。該文法使用了using關鍵字來完成這一工作—— 但目前,在完全不同的環境下,它與命名空間沒有關係。下面的代碼產生與try塊相對應的IL代碼:
using (ResourceGobbler theInstance = new ResourceGobbler()){ // 這裡是theInstance 對象的使用過程}
using語句的後面是一對圓括弧,其中是引用變數的聲明和執行個體化,該語句使變數放在隨附的複合陳述式中。另外,在變數超出範圍時,即使出現異常,也會自動調用其Dispose()方法。如果已經使用try塊來捕獲其他異常,就會比較清晰,如果避免使用using語句,僅在已有的try塊的finally子句中調用Dispose(),還可以避免進行額外的縮排。
注意:
對於某些類來說,使用Close()要比Dispose()更富有邏輯性,例如,在處理檔案或資料庫連接時,就是這樣。在這些情況下,常常實現IDisposable介面,再執行一個獨立的Close()方法,來調用Dispose()。這種方法在類的使用上比較清晰,還支援C#提供的using語句。
前面的章節討論了類所使用的釋放未託管資源的兩種方式:
● 利用運行庫強制執行的解構函式,但解構函式的執行是不確定的,而且,由於垃圾收集器的工作方式,它會給運行庫增加不可接受的系統開銷。
● IDisposable介面提供了一種機制,允許類的使用者控制釋放資源的時間,但需要確保執行Dispose()。
一般情況下,最好的方法是執行這兩種機制,獲得這兩種機制的優點,克服其缺點。假定大多數程式員都能正確調用Dispose(),實現IDisposable介面,同時把解構函式作為一種安全的機制,以防沒有調用Dispose()。下面是一個雙重實現的例子:
public class ResourceHolder : IDisposable{ private bool isDispose = false; // 顯示調用的Dispose方法 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } // 實際的清除方法 protected virtual void Dispose(bool disposing) { if (!isDisposed) { if (disposing) { // 這裡執行清除託管對象的操作. } // 這裡執行清除非託管對象的操作 } isDisposed=true; } // 解構函式 ~ResourceHolder() { Dispose (false); }}
可以看出,Dispose()有第二個protected重載方法,它帶一個bool參數,這是真正完成清理工作的方法。Dispose(bool)由解構函式和IDisposable.Dispose()調用。這個方式的重點是確保所有的清理代碼都放在一個地方。
傳遞給Dispose(bool)的參數表示Dispose(bool)是由解構函式調用,還是由IDisposable.Dispose()調用——Dispose(bool)不應從代碼的其他地方調用,其原因是:
● 如果客戶調用IDisposable.Dispose(),該客戶就指定應清理所有與該對象相關的資源,包括託管和非託管的資源。
● 如果調用了解構函式,在原則上,所有的資源仍需要清理。但是在這種情況下,解構函式必須由垃圾收集器調用,而且不應訪問其他託管的對象,因為我們不再能確定它們的狀態了。在這種情況下,最好清理已知的未託管資源,希望引用的託管對象還有解構函式,執行自己的清理過程。
isDispose成員變數表示對象是否已被刪除,並允許確保不多次刪除成員變數。這個簡單的方法不是安全執行緒的,需要調用者確保在同一時刻只有一個線程調用方法。要求客戶進行同步是一個合理的假定,在整個.NET類庫中反覆使用了這個假定(例如在集合類中)。最後,IDisposable.Dispose()包含一個對System.GC. SuppressFinalize()方法的調用。SuppressFinalize()方法則告訴垃圾收集器有一個類不再需要調用其解構函式了。因為Dispose()已經完成了所有需要的清理工作,所以解構函式不需要做任何工作。調用SuppressFinalize()就意味著垃圾收集器認為這個對象根本沒有解構函式.
正確理解以上內容,可以大大最佳化系統效能,及時釋放不需要的資料,不能僅靠C#提供的自動回收機制,也需要程式員使用更靈活的辦法!二者合一既能讓程式運行飛快,也讓系統更加穩定!