C#之托管與非托管資源

C#中的數(shù)據(jù)類型

C#中的所有類型都是(直接或間接)從System.Object類型派生的愤诱。
C#的類型被分成兩大類——引用類型(reference type)超营,分配在內(nèi)存堆上;值類型(value type),分配在堆棧上。如圖:


數(shù)據(jù)類型.png

值類型在棧里,先進后出冠跷,值類型變量的生命有先后順序,這個確保了值類型變量在退出作用域以前會釋放資源身诺。堆棧是從高地址往低地址分配內(nèi)存蜜托。

引用類型分配在托管堆(Managed Heap)上,聲明一個變量在棧上保存霉赡,當(dāng)使用new創(chuàng)建對象時橄务,會把對象的地址存儲在這個變量里。托管堆相反穴亏,從低地址往高地址分配內(nèi)存仪糖,如圖:

引用類型.png

托管資源

托管資源指的是.NET可以自動進行回收的資源,被CLR控制的內(nèi)存資源迫肖,這些資源的管理可以由CLR來控制锅劝,主要是指托管堆上分配的內(nèi)存資源。
托管資源的回收工作是不需要人工干預(yù)的蟆湖,由.NET運行庫在合適調(diào)用垃圾回收器進行回收故爵。
托管資源:從文字上看就是托付給別人管理,就像.NET的CLR隅津,java的jvm诬垂。
.NET中超過80%的資源都是托管資源。

非托管資源

非托管資源指的是.NET不知道如何回收的資源伦仍,是CLR不能控制或者管理的部分结窘,最常見的一類非托管資源是包裝操作系統(tǒng)資源的對象,例如文件充蓝,窗口隧枫,網(wǎng)絡(luò)連接數(shù)據(jù)庫連接谓苟,畫刷官脓,圖標等。
ApplicationContext, Brush, Component, ComponentDesigner, Container, Context, Cursor, FileStream, Font, Icon, Image, Matrix, Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, Timer, Tooltip, 文件句柄, GDI資源, 數(shù)據(jù)庫連接等等資源涝焙,這些都是非托管資源

這些資源一般情況下不存在于托管堆中卑笨。垃圾回收器在清理的時候會調(diào)用Object.Finalize()方法。默認情況下仑撞,方法是空的赤兴,對于非托管對象妖滔,需要在此方法中編寫回收非托管資源的代碼,以便垃圾回收器正確回收資源桶良。但在.NET中座舍,Object.Finalize()方法是無法重載的,編譯器是根據(jù)類的析構(gòu)函數(shù)來自動生成Object.Finalize()方法的艺普,所以對于包含非托管資源的類簸州,可以將釋放非托管資源的代碼放在析構(gòu)函數(shù)鉴竭。

?? 不能在析構(gòu)函數(shù)中釋放托管資源歧譬,因為析構(gòu)函數(shù)是有垃圾回收器調(diào)用的,可能在析構(gòu)函數(shù)調(diào)用之前搏存,類包含的托管資源已經(jīng)被回收了瑰步,從而導(dǎo)致無法預(yù)知的結(jié)果。

IDisposable接口

如果按照上面做法璧眠,非托管資源也能夠由垃圾回收器進行回收缩焦,但是非托管資源一般是有限的,比較寶貴的责静,而垃圾回收器是由CRL自動調(diào)用的袁滥,這樣就無法保證及時的釋放掉非托管資源,因此定義了一個Dispose()方法灾螃,讓使用者能夠手動的釋放非托管資源题翻。

  • Dispose()方法釋放類的托管資源和非托管資源,使用者手動調(diào)用此方法后腰鬼,垃圾回收器不會對此類實例再次進行回收嵌赠。
  • Dispose()方法是由使用者調(diào)用的,在調(diào)用時熄赡,類的托管資源和非托管資源肯定都未被回收姜挺,所以可以同時回收兩種資源。
  • 如果我們不想為一個類型實現(xiàn)Dispose方法彼硫,但我們想讓它自動的釋放非托管資源炊豪。那么就如上文所說,將釋放非托管資源的代碼放在析構(gòu)函數(shù)拧篮。

Microsoft為非托管資源的回收專門定義了一個接口:IDisposable溜在,接口中只包含一個Dispose()方法。任何包含非托管資源的類他托,都應(yīng)該繼承此接口掖肋。

在一個包含非托管資源的類中,關(guān)于資源釋放的標準做法是:
1)繼承IDisposable接口赏参;
2)實現(xiàn)Dispose()方法志笼,在其中釋放托管資源和非托管資源沿盅,并將對象本身從垃圾回收器中移除(垃圾回收器不在回收此資源);
3)實現(xiàn)類析構(gòu)函數(shù)纫溃,在其中釋放非托管資源腰涧。
只要按照上面要求的步驟編寫代碼,該類就屬于資源安全的類紊浩。

解釋:在使用時窖铡,顯示調(diào)用Dispose()方法,可以及時的釋放資源坊谁,同時通過移除Finalize()方法的執(zhí)行费彼,提高了性能;如果沒有顯示調(diào)用Dispose()方法口芍,垃圾回收器也可以通過析構(gòu)函數(shù)來釋放非托管資源箍铲,垃圾回收器本身就具有回收托管資源的功能,從而保證資源的正常釋放鬓椭,只不過由垃圾回收器回收會導(dǎo)致非托管資源的未及時釋放的浪費颠猴。

??在.NET中應(yīng)該盡可能的少用析構(gòu)函數(shù)釋放資源。在沒有析構(gòu)函數(shù)的對象在垃圾處理器一次處理中從內(nèi)存刪除小染,但有析構(gòu)函數(shù)的對象翘瓮,需要兩次,第一次調(diào)用析構(gòu)函數(shù)裤翩,第二次刪除對象资盅。而且在析構(gòu)函數(shù)中包含大量的釋放資源代碼,會降低垃圾回收器的工作效率岛都,影響性能律姨。所以對于包含非托管資源的對象,最好及時的調(diào)用Dispose()方法來回收資源臼疫,而不是依賴垃圾回收器择份。

附上MSDN的代碼,大家可以參考烫堤。

public class BaseResource : IDisposable
{
    // 指向外部非托管資源
    private IntPtr handle;
    // 此類使用的其它托管資源.
    private Component Components;
    // 跟蹤是否調(diào)用.Dispose方法荣赶,標識位,控制垃圾收集器的行為
    private bool disposed = false;
    // 構(gòu)造函數(shù)
    public BaseResource()
    {
        // Insert appropriate constructor code here.
    }
    // 實現(xiàn)接口IDisposable.
    // 不能聲明為虛方法virtual.
    // 子類不能重寫這個方法.
    public void Dispose()
    {
        Dispose(true);
        // 離開終結(jié)隊列Finalization queue
        // 設(shè)置對象的阻止終結(jié)器代碼
        GC.SuppressFinalize(this);
    }
    // Dispose(bool disposing) 執(zhí)行分兩種不同的情況.
    // 如果disposing 等于 true, 方法已經(jīng)被調(diào)用
    // 或者間接被用戶代碼調(diào)用. 托管和非托管的代碼都能被釋放
    // 如果disposing 等于false, 方法已經(jīng)被終結(jié)器 finalizer 從內(nèi)部調(diào)用過鸽斟,
    // 你就不能在引用其他對象拔创,只有非托管資源可以被釋放。
    protected virtual void Dispose(bool disposing)
    {
        // 檢查Dispose 是否被調(diào)用過.
        if (!this.disposed)
        {
            // 如果等于true, 釋放所有托管和非托管資源
            if (disposing)
            {
                // 釋放托管資源.
                Components.Dispose();
            }
            // 釋放非托管資源富蓄,如果disposing為 false,
            // 只會執(zhí)行下面的代碼.
            CloseHandle(handle);
            handle = IntPtr.Zero;
            // 注意這里是非線程安全的.
            // 在托管資源釋放以后可以啟動其它線程銷毀對象剩燥,
            // 但是在disposed標記設(shè)置為true前
            // 如果線程安全是必須的,客戶端必須實現(xiàn)。
        }
        disposed = true;
    }
    // 使用interop 調(diào)用方法
    // 清除非托管資源.
    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);
    // 使用C# 析構(gòu)函數(shù)來實現(xiàn)終結(jié)器代碼
    // 這個只在Dispose方法沒被調(diào)用的前提下灭红,才能調(diào)用執(zhí)行侣滩。
    // 如果你給基類終結(jié)的機會.
    // 不要給子類提供析構(gòu)函數(shù).
    ~BaseResource()
    {
        // 不要重復(fù)創(chuàng)建清理的代碼.
        // 基于可靠性和可維護性考慮,調(diào)用Dispose(false) 是最佳的方式
        Dispose(false);
    }
    // 允許你多次調(diào)用Dispose方法,
    // 但是會拋出異常如果對象已經(jīng)釋放变擒。
    // 不論你什么時間處理對象都會核查對象的是否釋放,
    // check to see if it has been disposed.
    public void DoSomething()
    {
        if (this.disposed)
        {
            thrownew ObjectDisposedException();
        }
    }
    // 不要設(shè)置方法為virtual.
    // 繼承類不允許重寫這個方法
    public void Close()
    {
        // 無參數(shù)調(diào)用Dispose參數(shù).
        Dispose();
    }
    public static void Main()
    {
        // Insert code here to create
        // and use a BaseResource object.
    }
}

.NET Framework的System.GC類提供了控制Finalize的兩個方法君珠,ReRegisterForFinalize和SuppressFinalize。前者是請求系統(tǒng)完成對象的Finalize方法娇斑,后者是請求系統(tǒng)不要完成對象的Finalize方法策添。當(dāng)你用Dispose方法釋放未托管對象的時候,應(yīng)該調(diào)用GC.SuppressFinalize毫缆。GC.SuppressFinalize會阻止GC調(diào)用Finalize方法唯竹。因為Finalize方法的調(diào)用會犧牲部分性能。如果你的Dispose方法已經(jīng)對委托管資源作了清理悔醋,就沒必要讓GC再調(diào)用對象的Finalize方法(MSDN)摩窃。

析構(gòu)函數(shù)只能由垃圾回收器調(diào)用兽叮。
Despose()方法只能由類的使用者調(diào)用芬骄。

在C#中,凡是繼承了IDisposable接口的類鹦聪,都可以使用using語句账阻,從而在超出作用域后,讓系統(tǒng)自動調(diào)用Dispose()方法泽本。 一個資源安全的類淘太,都實現(xiàn)了IDisposable接口和析構(gòu)函數(shù)。提供手動釋放資源和系統(tǒng)自動釋放資源的雙保險规丽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒲牧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子赌莺,更是在濱河造成了極大的恐慌冰抢,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艘狭,死亡現(xiàn)場離奇詭異挎扰,居然都是意外死亡,警方通過查閱死者的電腦和手機巢音,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門遵倦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人官撼,你說我怎么就攤上這事梧躺。” “怎么了傲绣?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵掠哥,是天一觀的道長棘脐。 經(jīng)常有香客問我,道長龙致,這世上最難降的妖魔是什么蛀缝? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮目代,結(jié)果婚禮上屈梁,老公的妹妹穿的比我還像新娘。我一直安慰自己榛了,他們只是感情好在讶,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著霜大,像睡著了一般构哺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上战坤,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天曙强,我揣著相機與錄音,去河邊找鬼途茫。 笑死碟嘴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的囊卜。 我是一名探鬼主播娜扇,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼栅组!你這毒婦竟也來了雀瓢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤玉掸,失蹤者是張志新(化名)和其女友劉穎刃麸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體排截,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡嫌蚤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了断傲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脱吱。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖认罩,靈堂內(nèi)的尸體忽然破棺而出箱蝠,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布宦搬,位于F島的核電站牙瓢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏间校。R本人自食惡果不足惜矾克,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望憔足。 院中可真熱鬧胁附,春花似錦、人聲如沸滓彰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揭绑。三九已至弓候,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間他匪,已是汗流浹背菇存。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留诚纸,地道東北人撰筷。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓陈惰,卻偏偏與公主長得像畦徘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抬闯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359