C#基礎(chǔ):Dispose()、Close()、Finalize()的區(qū)別詳解
.net內(nèi)存回收與Dispose﹐Close﹐Finalize方法
一. net的對(duì)象使用一般分為三種情況﹕
1.創(chuàng)建對(duì)象
2.使用對(duì)象
3.釋放對(duì)象
二.創(chuàng)建對(duì)象
1.創(chuàng)建對(duì)象實(shí)際分為兩個(gè)步驟﹕變量類型宣告和初始化對(duì)象
2.變量類型宣告(declare),如﹕
FileStream fs
這行代碼會(huì)在當(dāng)前的變量作用域空間(?;蚨?里建立一個(gè)叫做fs的變量﹐至少四個(gè)字節(jié)吧(因?yàn)橐嬉粋€(gè)對(duì)象的地址)
3.初始化對(duì)象
對(duì)象在使用(調(diào)用其方法或?qū)傩?前﹐必須進(jìn)行初始化。
如﹕
fs = new FileStream(@"C:\test.txt",FileMode.OpenOrCreate);
這行代碼會(huì)分成3個(gè)步驟﹕
a.在托管堆中分配一塊內(nèi)存﹐其大小等于FileStream中所有字段(當(dāng)然不包括靜態(tài)的)的內(nèi)存總和加上MS認(rèn)為需要的其它東東。
b.初始化對(duì)象的字段(值類型的把其位全部初始化成0,對(duì)象初始化為null﹐當(dāng)然string是一個(gè)例外﹐它被初始化成空字符串)
c.調(diào)用FileStream相應(yīng)的構(gòu)造器﹐這里會(huì)初始化一個(gè)非托管資源(文件)的私有字段。
三.使用對(duì)象
使用對(duì)象就沒(méi)什么講的﹐就是調(diào)用對(duì)象的方法(或?qū)傩缘?來(lái)完成某個(gè)功能當(dāng)然為了釋放對(duì)象而調(diào)用的方法其范疇?wèi)?yīng)不屬于此類中(現(xiàn)在提到的Finalize等)
四.釋放對(duì)象
1.釋放對(duì)象也就是說(shuō)這個(gè)對(duì)象我已經(jīng)不需要了﹐現(xiàn)在我要把其釋放﹐以便把其在堆上所占用的內(nèi)存空間給收回來(lái)(當(dāng)然變量名的內(nèi)存空間就不需要管了﹐因?yàn)樗鼤?huì)隨其作用域自動(dòng)消失)
2. .net自動(dòng)進(jìn)行內(nèi)存管理﹐也就是說(shuō)當(dāng)它判斷一個(gè)對(duì)象沒(méi)有用了(當(dāng)然有自己的算法)﹐它就會(huì)將其內(nèi)存給自動(dòng)收回來(lái)﹐但是其收回的時(shí)間一般不確定(當(dāng).net認(rèn)為內(nèi)存緊張時(shí)﹐它就會(huì)開(kāi)始)
BTW:其實(shí)我們就是想自己收回對(duì)象的內(nèi)存也不可能﹐因?yàn)镸S沒(méi)有提供途徑(GC.Collect也是啟動(dòng).net的內(nèi)存收集功能)
五.第一個(gè)結(jié)論
在net中使用對(duì)象很簡(jiǎn)單﹐創(chuàng)建對(duì)象之后直接使用就可以了﹐不用了也不要去管它﹐垃圾收集器會(huì)幫你把內(nèi)存要回來(lái)的。
六.例外
當(dāng)對(duì)象的成員引用了一個(gè)非托管資源時(shí)(不在托管堆上分配的內(nèi)存或資源﹐像文件﹐數(shù)據(jù)庫(kù)連接等等)﹐下面以一個(gè)例子來(lái)說(shuō)明﹕
System.IO.FileStream類別﹐這是.net基本類庫(kù)提供的一個(gè)非托管資源(文件)封裝對(duì)象(用Reflector工具反編譯mscorlib.dll可見(jiàn)其代碼)
1.FileStream毫無(wú)疑問(wèn)封裝了一個(gè)非托管資源
觀其源代碼發(fā)現(xiàn)有這樣一個(gè)私有成員﹕
private SafeFileHandle _handle;
通過(guò)構(gòu)造器調(diào)用的Init方法可以發(fā)現(xiàn)這個(gè)成員的初始化代碼﹕
this._handle = Win32Native.SafeCreateFile(text2, num1, share, secAttrs, mode, num2,
Win32Native.NULL);
而后者實(shí)際上就是kernel32.dll中的CreateFile方法﹐它返回一個(gè)HANDLE(即非托管資源引用)
2.我們先來(lái)使用這個(gè)類別﹕
using System;
using System.IO;
public class TestFileStream
{
public static void Main(string[] args)
{
//創(chuàng)建一個(gè)FileStream對(duì)象
FileStream fs = new FileStream(@"C:\test.txt",FileMode.OpenOrCreate);
Console.WriteLine("您可以嘗試在系統(tǒng)中刪除c盤下的test.txt(回車鍵繼續(xù))");
//暫停程序執(zhí)行﹐并嘗試在系統(tǒng)中刪除那個(gè)文件
Console.ReadLine();
//刪除文件測(cè)試
try
{
File.Delete(@"c:\test.txt");
}
catch (IOException ex)
{
Console.WriteLine("[Error]程序刪除文件失敗﹕{0}",ex.Message);
}
}
}
3.在程序掛起時(shí)(Console.ReadLine等待輸入)﹐刪除文件會(huì)失敗﹐很容易理解﹐因?yàn)槲募蜷_(kāi)后沒(méi)有將其關(guān)閉﹐系統(tǒng)不知道這個(gè)文件是否還有用﹐所以幫我們保護(hù)這個(gè)文件(理所當(dāng)然﹐那個(gè)非托管資源所使用的內(nèi)存還被程序占用著)
4.但是在程序執(zhí)行完后﹐我們?cè)賴L試刪除文件﹐成功﹗為什么?(fs不是沒(méi)有關(guān)閉那個(gè)SafeFileHandle嗎?)
當(dāng)然您可以說(shuō)﹐windows操作系統(tǒng)在一個(gè)進(jìn)程結(jié)束后會(huì)自動(dòng)回收其資源﹐沒(méi)錯(cuò)(但是如果是com就慘了﹐因?yàn)閏om是存在于自己的獨(dú)立進(jìn)程內(nèi)﹐而操作系統(tǒng)不負(fù)責(zé)這個(gè):( )﹐不過(guò)這里不是因?yàn)閣indows操作系統(tǒng)的功能﹐而是.net垃圾收集器幫的忙。
5.看下面這個(gè)例子
using System;
using System.IO;
public class TestFileStream
{
public static void Main(string[] args)
{
//創(chuàng)建一個(gè)FileStream對(duì)象
FileStream fs = new FileStream(@"C:\test.txt", FileMode.OpenOrCreate);
Console.WriteLine("您可以嘗試在系統(tǒng)中刪除c盤下的test.txt(回車鍵繼續(xù))");
//暫停程序執(zhí)行﹐并嘗試在系統(tǒng)中刪除那個(gè)文件
Console.ReadLine();
/**//*進(jìn)行垃圾收集*/
GC.Collect();
Console.WriteLine("再刪一下試試");
Console.ReadLine();
}
}
6.注意中間那行代碼:
GC.Collect();
這是強(qiáng)制要.net垃圾收集器進(jìn)行垃圾收集。
我們?cè)偃L試刪除test.txt﹐居然可以被刪除了﹐為什么呀?(fs不是沒(méi)有關(guān)閉那個(gè)SafeFileHandle嗎?)﹐讓我細(xì)細(xì)道來(lái)﹕
7.我們首先了解一下.net垃圾收集器進(jìn)行垃圾收集的四種時(shí)機(jī)(參見(jiàn)﹕.net框架程序設(shè)計(jì) 李建忠譯)
a.最常見(jiàn)的﹕當(dāng).net覺(jué)得合適時(shí)﹐例如它感到內(nèi)存緊張了(朮語(yǔ)稱為﹕0代對(duì)象充滿)
b.微軟強(qiáng)烈不建議使用的﹕GC的Collect方法調(diào)用(就是我們上面用的這種啦﹐因?yàn)闀?huì)降低性能﹐會(huì)掛起進(jìn)程, 等等﹐反正聽(tīng)微軟的吧。當(dāng)然某些時(shí)候可以用﹐就像我上面用來(lái)測(cè)試的代碼﹐呵呵...)
c.應(yīng)用程序域卸載時(shí)(AppDomain)
d.CLR被關(guān)閉時(shí)
8.現(xiàn)在我們可以明白第1個(gè)例子為什么在程序結(jié)束后文件可以被刪除﹐因?yàn)镃LR被關(guān)閉時(shí)﹐.net執(zhí)行了垃圾收集(也就是等同于第二個(gè)例子的GC.Collect()代碼)
9.所以現(xiàn)在所有的問(wèn)題都集中到垃圾收集上面﹐它做了什么?
a.垃圾收集器在判斷一個(gè)對(duì)象不會(huì)再被引用到后﹐就開(kāi)始對(duì)它進(jìn)行垃圾收集(即回收內(nèi)存)
b.清空內(nèi)存(即把托管堆中的內(nèi)存收回來(lái))
c.但是對(duì)象的有些字段引用到了非托管資源怎么辦?如FileStream的_handle
d.所以我們必須告訴垃圾收集器﹐在你回收我的內(nèi)存之前﹐先幫我執(zhí)行一個(gè)方法來(lái)收回我的非托管資源﹐以免托管堆的內(nèi)存被你回收了﹐而我引用的非托管資源的內(nèi)存卻被泄漏了。
e.這個(gè)方法就是Finalize()﹐也就是C#的 ~ClassName() 方法(同C++中的析構(gòu)語(yǔ)法)
f.所以一個(gè)對(duì)象如果存在Finalize方法時(shí)﹐垃圾收集器在收回它的內(nèi)存之前就會(huì)自動(dòng)調(diào)用這個(gè)方法
g.這樣我們就可以把那些東東(非托管資源)給清理干凈了
由此看來(lái)﹐垃圾收集器提供的這種機(jī)制就是為了更好的完善.net的自動(dòng)內(nèi)存管理的功能﹐讓我們也可以參與到垃圾收集中去
10.我們?cè)賮?lái)看看GC.Collect()這行代碼或CLR關(guān)閉時(shí).Net做了什么﹕
a.垃圾收集器啟動(dòng)﹐發(fā)現(xiàn)fs引用的那個(gè)對(duì)象已經(jīng)沒(méi)用了(當(dāng)然CLR關(guān)閉時(shí)才不管你有沒(méi)有用﹐通通回收)﹐于是對(duì)它進(jìn)行內(nèi)存回收
b.發(fā)現(xiàn)fs的類型﹕FileStream提供了Finalize方法﹐于是先調(diào)用這個(gè)方法
(以下通過(guò)Reflector繼續(xù))
c.Finalize方法中有 this._handle.Dispose()代碼﹐于是調(diào)用SafeHandler.Dispose()
d.接著轉(zhuǎn)到(當(dāng)然好多個(gè)圈﹐您悠著點(diǎn)...)SafeFileHandle.ReleaseHandle方法﹐發(fā)現(xiàn)代碼﹕Win32Native.CloseHandle() (即關(guān)閉非托管資源--文件HANDLE)
真相大白﹕原來(lái)是垃圾收集器幫我們關(guān)閉了那個(gè)非托管資源(當(dāng)然還是通過(guò)我們自己寫的Finalize方法)﹐因此后面就可以刪除文件了。
11.有人會(huì)問(wèn)﹕好像我們平時(shí)在使用FileStream對(duì)象時(shí)﹐沒(méi)這么復(fù)雜呀?
答﹕Very Good!
一部分人﹕是因?yàn)榇蠹叶己臀业睦?一樣有好運(yùn)氣﹐那個(gè)C盤下的test.txt文件自從被創(chuàng)建后﹐我壓根就不會(huì)再去用它﹐管它這部分資源有沒(méi)有被泄漏﹐有沒(méi)有被鎖定﹐最后程序結(jié)束時(shí)﹐被垃圾收集器幫了忙﹐把忘了關(guān)閉的文件HANDLE給收回來(lái)了。
剩下的一部分人﹕在程序里埋下了一顆"啞彈"﹐不知什么時(shí)候會(huì)爆炸﹐就像我例子中的File.Delete方法就出現(xiàn)了異常。
(不過(guò)我覺(jué)得)絕大多數(shù)人﹕是在看了很多諸如.net編程忠告﹐Microsoft強(qiáng)烈建議﹐MSDN標(biāo)準(zhǔn)做法等等等等( 還有我這篇blog﹐呵呵)之后﹐知道了在使用如FileStream,SqlConnection這些東東時(shí)﹐必須將其Close。
12.Close與Dispose
查看我們那兩個(gè)例子的代碼﹐都是不標(biāo)準(zhǔn)的﹐正確做法應(yīng)該在使用完那個(gè)FileStream后﹐調(diào)用fs.Close()將其關(guān)閉﹐以保證資源的安全。
附﹕正確做法
using System;
using System.IO;
public class TestFileStream
{
public static void Main(string[] args)
{
//創(chuàng)建一個(gè)FileStream對(duì)象
FileStream fs = new FileStream(@"C:\test.txt", FileMode.OpenOrCreate);
/**//*在用完FileStream后關(guān)閉*/
fs.Close();
//刪除文件測(cè)試
try
{
File.Delete(@"c:\test.txt");
}
catch (IOException ex)
{
Console.WriteLine("[Error]程序刪除文件失敗﹕{0}", ex.Message);
}
}
}
13.有人舉手﹐講這么多﹐早告訴我調(diào)用fs.Close不就得了。
哥們﹐fs.Close()方法是由您寫的﹐調(diào)不調(diào)用﹐手在您身上﹐您不調(diào)用的話﹐哪天程序出了問(wèn)題﹐您有會(huì)叫﹕微軟真垃圾﹐.net真不穩(wěn)定﹐還是java好﹐安全﹐可靠... 為防您的國(guó)罵﹐MS只好在垃圾收集中加這一款﹐以防不測(cè)...
14.Dispose模式
認(rèn)真查看.net類庫(kù)中的那些基本類別﹐凡是有Finalize方法的類別﹐基本上都提供了諸如Dispose,Close,Dispose(bool)等方法(FileStream也不例外)
15.其實(shí)不管是Dispose,Close,Finalize方法﹐最終應(yīng)該都是執(zhí)行相同的代碼
區(qū)別﹕
Finalize方法﹕只能由微軟調(diào)用
Dispose和Close方法﹕提供給您調(diào)用
因此在您使用完那些類別后﹐那就直接調(diào)用Close吧(沒(méi)有Close﹐再調(diào)用Dispose方法)﹐當(dāng)然萬(wàn)一您忘了﹐也別擔(dān)心﹐還有垃圾收集器幫您墊后。
七.第二個(gè)結(jié)論﹕
1.在您開(kāi)發(fā)一個(gè)封裝非托管資源(即類中的字段引用到了非托管資源)的類別時(shí)﹕
A:強(qiáng)烈建議您提供Finalize方法進(jìn)行非托管資源的釋放﹐.net垃圾收集器不會(huì)幫您自動(dòng)回收那部分資源﹐而是通過(guò)調(diào)用您的Finalize方法來(lái)幫您釋放。(這樣可以保證﹕在使用您類別的那位程序員忘了手動(dòng)回收內(nèi)存時(shí)﹐還可通過(guò)垃圾收集器來(lái)補(bǔ)救)
B.強(qiáng)烈建議您提供一個(gè)Close或Dispose方法﹐以便使用您類別的程序員可以手動(dòng)釋放您的類別中的非托管資源。(參見(jiàn).net框架程序設(shè)計(jì) 自動(dòng)內(nèi)存管理一章實(shí)現(xiàn)Dispose模式)
C.如果類別封裝了像FileStream這樣的對(duì)象(即對(duì)非托管資源的再次封裝)時(shí)﹐一般也應(yīng)該提供一 個(gè)Close或Dispose方法﹐除非您的這個(gè)成員保證在每次使用后﹐都被正常的關(guān)閉﹐即對(duì)調(diào)用者透明。
2.在您使用一個(gè)封裝非托管資源的類別時(shí)﹕
A:強(qiáng)烈建議您在明確知道這個(gè)類別沒(méi)有用之后﹐調(diào)用其提供的Close或Dispose方法手動(dòng)釋放其非托管資源的 內(nèi)存。有道是﹕有借有還﹐再借不難;借了不還﹐再借休想~~
B:注意在手動(dòng)釋放后﹐不要再調(diào)用該對(duì)象的相關(guān)方法了﹐因?yàn)閷?duì)象已經(jīng)損毀了
再次BTW:不管是Finalize﹐Close還是Dispose﹐您都無(wú)法顯式釋放托管堆內(nèi)存﹐它們永遠(yuǎn)是微軟的"私人財(cái)產(chǎn) "﹕)
總結(jié):Dispose 和 Close基本上是一樣的。 Close 是為那些不熟悉Dispose的開(kāi)發(fā)者設(shè)計(jì)的,Close讓人更容易理解是做什么的。
在.net framework 里面,close()被設(shè)計(jì)成public的,并且在close()里面調(diào)用被隱藏的dispose(),而后dispose()再去調(diào)用另一個(gè)virtual的dispose(bool)
函數(shù)。所以如果從這個(gè)class繼承,你就必須實(shí)現(xiàn)dispose(bool)方法 。調(diào)用者通過(guò)close()就會(huì)間接調(diào)用到你重載的那個(gè)dispose(bool)方法去釋放資源。
因?yàn)閏lose()只是用來(lái)呼叫那個(gè)隱藏的dispose()繼而呼叫dispose(bool)的,用戶不應(yīng)該改變close行為的
具有disponse()方法的類是實(shí)現(xiàn) 了IDisposable 接口的。 .net中有很多class 只提供close(),而不對(duì)外提供disponse(),但是它的確實(shí)現(xiàn)了idisponse接口,
這是為什么呢?
究其原因是因?yàn)榻涌诘膶?shí)現(xiàn)模式 ---- 顯式實(shí)現(xiàn) 隱式實(shí)現(xiàn),二者的區(qū)別:對(duì)于 隱式實(shí)現(xiàn) 來(lái)說(shuō),你只需要調(diào)用 “new ClassA().Dispose()“,但是對(duì)于顯式實(shí)現(xiàn)
來(lái)說(shuō),dispose()不會(huì)是這個(gè) classA 的成員函數(shù)。唯一的調(diào)用方式是先強(qiáng)制類型轉(zhuǎn)換到 IDisposable ,即"new ClassA().Dispose()",但是((IDisposable)new ClassA()).Dispose() 可以編譯過(guò)。這樣就符合了設(shè)計(jì)的要求:提供 close(),隱藏dispose(),并且實(shí)現(xiàn) IDisposeable接口
相關(guān)文章
C# 實(shí)現(xiàn)Eval(字符串表達(dá)式)的三種方法
這篇文章主要介紹了C# 實(shí)現(xiàn)Eval(字符串表達(dá)式)的三種方法,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-04-04C#使用Post調(diào)用接口并傳遞json參數(shù)
這篇文章主要介紹了C#使用Post調(diào)用接口并傳遞json參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-06-06C#串口編程System.IO.Ports.SerialPort類
這篇文章介紹了C#串口編程System.IO.Ports.SerialPort類,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06