本節導讀:雖然在.NET程式設計過程中,絕大多數記憶體垃圾回收由CLR(公共語言執行時)自動回收,但也有很多需要我們編碼回收。掌握託管與非託管的基本知識,可以有效避免某些情況下導致的程式異常。
1.什麼是託管與非託管?
託管資源:一般是指被CLR(公共語言執行時)控制的記憶體資源,這些資源由CLR來管理。可以認為是.net 類庫中的資源。
非託管資源:不受CLR控制和管理的資源。
對於託管資源,GC負責垃圾回收。對於非託管資源,GC可以跟蹤非託管資源的生存期,但是不知道如何釋放它,這時候就要人工進行釋放。
2.哪些資源是非託管的?
總體來說就是 不受CLR控制和管理的資源
包括:比如檔案流、影象圖形類、資料庫的連線,網路連線,系統的視窗控制程式碼,印表機資源等,這類資源一般不存在堆上。可以認為作業系統資源的一組API。
原則:如果我們的類使用的非託管資源,如資料庫連線、檔案控制程式碼,這些資源需做到:使用後立刻釋放。
3.如何釋放非託管資源?
.NET對於釋放資源,標準做法如下:
(1)繼承IDisposable介面;
(2)實現Dispose()方法,在其中釋放託管資源和非託管資源,並將物件本身從垃圾回收器中移除(垃圾回收器不在回收此資源);
(3) 實現類解構函式,在其中釋放非託管資源。
對於Dispose()方法的幾個引數說明:
A. 引數為true表示釋放所有資源,只能由使用者呼叫
引數為false表示釋放非託管資源,只能由垃圾回收器自動呼叫
C. 如果子類有自己的非託管資源,可以過載這個函式,新增自己的非託管資源的釋放
D.但是要記住,過載此函式必須保證呼叫基類的版本,以保證基類的資源正常釋放
4.舉例說明資源釋放
4.1 資料庫連線釋放
(1)錯誤做法:
SqlConnection conn = new SqlConnection();
//do something;
conn.Dispose();
此段程式碼,如果出現異常conn.Dispose()未能及時執行,會導致沒有及時關閉連線。
(2)正確做法:
SqlConnection conn = new SqlConnection(); try { //do something; } finally { conn.Dispose(); }
對於以上程式碼,我們還可以簡化程式碼量,c#使用using簡化輸入,編譯器自動翻譯成 try...finally,改為下面寫法
(3)改進做法:
using(SqlConnection conn = new SqlConnection()) { //do something; }
4.2 資源回收綜合示例
public class BaseResource : IDisposable { private IntPtr handle; // 控制程式碼,屬於非託管資源 private System.ComponentModel.Component comp; // 元件,託管資源 private bool isDisposed = false; // 是否已釋放資源的標誌 //實現介面方法 //由類的使用者,在外部顯示呼叫,釋放類資源 public void Dispose() { Dispose(true);// 釋放託管和非託管資源 //將物件從垃圾回收器連結串列中移除, // 從而在垃圾回收器工作時,只釋放託管資源,而不執行此物件的解構函式 GC.SuppressFinalize(this); } //由垃圾回收器呼叫,釋放非託管資源 ~BaseResource() { Dispose(false);// 釋放非託管資源 } //引數為true表示釋放所有資源,只能由使用者呼叫 //引數為false表示釋放非託管資源,只能由垃圾回收器自動呼叫 //如果子類有自己的非託管資源,可以過載這個函式,新增自己的非託管資源的釋放 //但是要記住,過載此函式必須保證呼叫基類的版本,以保證基類的資源正常釋放 protected virtual void Dispose(bool disposing) { if (!this.isDisposed)// 如果資源未釋放 這個判斷主要用了防止物件被多次釋放 { if (disposing) { comp.Dispose();// 釋放託管資源 } } this.isDisposed = true; // 標識此物件已釋放 } }
5.對於釋放資源注意的幾點
A. 顯示呼叫Dispose()方法,可以及時的釋放資源,同時通過移除Finalize()方法的執行,提高了效能;
B. 如果沒有顯式呼叫Dispose()方法,垃圾回收器也可以通過解構函式來釋放非託管資源,垃圾回收器本身就具有回收託管資源的功能,從而保證資源的正常釋放,只不過由垃圾回收器回收會導致非託管資源的未及時釋放的浪費。
C. 在.NET中應該儘可能的少用解構函式釋放資源。在沒有解構函式的物件在垃圾處理器一次處理中從記憶體刪除,但有解構函式的物件,需要兩次,第一次呼叫解構函式,第二次刪除物件。而且在解構函式中包含大量的釋放資原始碼,會降低垃圾回收器的工作效率,影響效能。
D. 對於包含非託管資源的物件,最好及時的呼叫Dispose()方法來回收資源,而不是依賴垃圾回收器
E. 解構函式只能由垃圾回收器呼叫。
F. Despose()方法只能由類的使用者呼叫。
G. 在.NET中,凡是繼承了IDisposable介面的類,都可以使用using語句,從而在超出作用域後,讓系統自動呼叫Dispose()方法。
H. 一個資源安全的類,都實現了IDisposable介面和解構函式。提供手動釋放資源和系統自動釋放資源的雙保險。
6. 關於Finalize和Dispose
(1)、Finalize只釋放非託管資源;
(2)、Dispose釋放託管和非託管資源;
(3)、重複呼叫Finalize和Dispose是沒有問題的;
(4)、Finalize和Dispose共享相同的資源釋放策略,因此他們之間也是沒有衝突的。
7. 要點:
本節內容比較簡單,主要了解了.NET託管和非託管兩種資源,絕大多部資源由CLR(公共語言執行時)自動釋放,稱為託管程式碼;還有一部分資源需要人工釋放,稱為非託管程式碼。掌控非託管程式碼的幾種記憶體釋放方法,可以以效的避免程式產生異常,合理釋放記憶體,有利於程式穩定和流暢。
==============================================================================================
<如果對你有幫助,記得點一下推薦哦,如有
有不明白或錯誤之處,請多交流>
<對本系列文章閱讀有困難的朋友,請先看《.net 物件導向程式設計基礎》>
<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>
==============================================================================================