C#中IDispose接口的實(shí)現(xiàn)及為何這么實(shí)現(xiàn)詳解
前言
我原本認(rèn)為對(duì)于IDispose的實(shí)現(xiàn)方法,只要在里面釋放非托管資源就行了,但是通過(guò)網(wǎng)上資料,看到很多實(shí)現(xiàn)方法并不是僅僅做釋放非托管資源,非常迷惑,關(guān)鍵是這些資料也沒(méi)詳細(xì)的告訴你為什么這么做?之后通過(guò)StackOverflow了解到這一步一步的原因,說(shuō)的十分詳細(xì),結(jié)合自己的認(rèn)識(shí),翻譯后分享給大家:
一、IDispose的實(shí)現(xiàn)方法
具體的實(shí)現(xiàn)方法,你可以直接查看這個(gè)腳本之家網(wǎng)站的教程:
http://www.dbjr.com.cn/article/54899.htm
如果你能看懂,并且很清楚為什么那么做。那么以下的文章你就可以略去不看。如果不清楚為什么那么做,請(qǐng)帶著你的迷惑往下看:
二、為什么那樣實(shí)現(xiàn)
英文好的可以直接去StackOverflow原文地址:
https://stackoverflow.com/questions/538060/proper-use-of-the-idisposable-interface/538238#538238
2.1、進(jìn)行之前
在C++中,所有你在堆上申請(qǐng)的內(nèi)存空間,必須手動(dòng)釋放掉,否則就會(huì)造成內(nèi)存的泄露。這可能會(huì)讓你在寫(xiě)程序的時(shí)候要花點(diǎn)心思在內(nèi)存的管理上而不是專(zhuān)注于解決你編程的目的—解決問(wèn)題。所以作為C++的進(jìn)化版C#使用了GC(Garbage Collector)來(lái)進(jìn)行內(nèi)存的管理以達(dá)到自動(dòng)釋放不需要的內(nèi)存的目的,但是GC并不能做的十分完美,對(duì)于一些非托管資源,GC無(wú)能為力,這就要求我們必須手動(dòng)的釋放那么非托管資源,為了更好的去做到這一點(diǎn),我們就要編寫(xiě)一種方法,通過(guò)手動(dòng)調(diào)用這個(gè)方法,我們就能夠釋放掉非托管資源。
注:
什么是托管資源和非托管資源?
托管資源就是托管給CLR的資源,CLR能對(duì)這些資源進(jìn)行管理。而非托管資源則是CLR無(wú)法對(duì)這些資源管理,這些資源的申請(qǐng)、釋放必須由使用者自行管理。
例如,像Win32編程中的文件句柄,上下文句柄、窗口或網(wǎng)絡(luò)連接等資源都屬于非托管資源。但是如果這些非托管資源在.Net中進(jìn)行了封裝,成為了.Net類(lèi)庫(kù)中的一部分,它就不屬于非托管資源了,因?yàn)樵趯?duì)它們封裝的過(guò)程中,就實(shí)現(xiàn)了它們的自動(dòng)管理功能。
也就是說(shuō),你能在.Net中找到的類(lèi)產(chǎn)生的對(duì)象,都是托管資源。
(理解這點(diǎn)很重要,這可能是你看不懂上面實(shí)現(xiàn)教程的重要一個(gè)原因!)
注:
GC進(jìn)行垃圾回收的時(shí)間和順序?
GC進(jìn)行垃圾回收的時(shí)間我們根本無(wú)法確定(當(dāng)然你手動(dòng)調(diào)用GC的垃圾回收方法除外),并且順序也不能確定!也就是說(shuō),你先申請(qǐng)的空間有可能在你后申請(qǐng)的空間釋放之后釋放。
GC對(duì)于實(shí)現(xiàn)析構(gòu)函數(shù)和沒(méi)實(shí)現(xiàn)析構(gòu)函數(shù)的類(lèi)處理方法不一樣,簡(jiǎn)單些說(shuō)GC對(duì)于實(shí)現(xiàn)了析構(gòu)函數(shù)的類(lèi)一定會(huì)調(diào)用他們的析構(gòu)函數(shù)。
關(guān)于.Net的垃圾回收機(jī)制,你可以暫時(shí)先知道這么多,待看完了這篇文章再去深入了解。
2.2、我們需要編寫(xiě)一種方法去釋放!
為了去清除一些非托管資源,你創(chuàng)建的類(lèi)需要有一個(gè)public方法,方法的名字可以隨意命名
例如:
public void Cleanup() public void Shutdown() ……
你可以這么做,但是有一個(gè)標(biāo)準(zhǔn)的名字
public void Dispose()
甚至有一個(gè)接口IDisposeable,里面包含的就是剛才那個(gè)方法
public interface IDisposable
{
void Dispose()
}
因此最好的辦法是讓你的類(lèi)去實(shí)現(xiàn)IDisposable接口,在接口內(nèi)的Dispose方法內(nèi)提供一段清除非托管資源的代碼。
public void Dispose()
{
//這里釋放一個(gè)句柄(句柄是一個(gè)非托管資源,屬于Win32編程的概念)
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
OK。這就完成了,除非你想做的更好!
2.3、別忘了類(lèi)中的托管資源還占著空間!
托管資源占著空間?你首先想到的可能是那些int,string等等這些托管資源,它們能占用幾個(gè)空間,他們占著就占著唄!
但是托管資源可不僅僅是那些資源,要是你的對(duì)象使用了250MB的System.Drawing,Bitmap(這是在.Net Frame中的,屬于托管資源)作為一些緩沖怎么辦?當(dāng)然,你知道這是一個(gè).Net的托管資源,所以GC理所應(yīng)當(dāng)?shù)膶?huì)釋放它。但是你真的想留著250MB的內(nèi)存空間就那么被占用著?然后等待著GC最終釋放它?更或者要是有一個(gè)更大數(shù)據(jù)庫(kù)連接呢?我們當(dāng)然不想讓那連接白白占用來(lái)等待GC的終結(jié)!
如果用戶調(diào)用了Dispose方法(意味著他們不再想使用這個(gè)對(duì)象里的一切)為什么不去扔掉那些浪費(fèi)空間的位圖資源和數(shù)據(jù)庫(kù)連接呢?
那么,我們就應(yīng)該這么做:
- 釋放非托管資源(因?yàn)槲覀儽仨氝@么做)
- 釋放托管資源(讓你的Dispose更完美)
所以,讓我們更新我們的Dispose方法來(lái)釋放那些托管資源
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection !=null)
{
this.databaseConnection.Dispose();
this.databaseConnection =null;
}
if (this.frameBufferImage !=null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
OK,做的很好了,除非你想做的更好!
2.4、總會(huì)有人粗心忘記調(diào)用Dispose!
要是有人使用你的類(lèi)創(chuàng)建了對(duì)象,但是忘記調(diào)用Dispose方法該怎么辦?這將會(huì)泄露一些非托管的資源!
注意:忘記調(diào)用Dispose方法雖然會(huì)造成非托管資源的泄露,但是對(duì)于那些托管資源來(lái)說(shuō),是不會(huì)泄露的,因?yàn)樽罱KGC會(huì)行動(dòng)起來(lái),在后臺(tái)線程中釋放那些和托管資源有關(guān)的內(nèi)存空間。這包括你創(chuàng)建的對(duì)象和其中的托管資源(例如Bitmap和數(shù)據(jù)庫(kù)連接) (為什么?往下看)
也就是說(shuō),如果你忘記調(diào)用Dispose方法,這個(gè)類(lèi)應(yīng)該自動(dòng)進(jìn)行一些補(bǔ)救措施!我們可以想到設(shè)計(jì)一種方法來(lái)做為一種后備方法:利用GC最終調(diào)用的終結(jié)器
注意:GC最終雖然會(huì)釋放托管資源,但是GC并不知道或者關(guān)心你的Dispose方法。那僅僅是一個(gè)我們選擇的名字。
GC調(diào)用析構(gòu)函數(shù)是一個(gè)完美的時(shí)機(jī)來(lái)釋放托管資源,我們實(shí)現(xiàn)析構(gòu)函數(shù)的功能通過(guò)重寫(xiě)Finalize方法。
注意:在C#中,你不能真的去使用重寫(xiě)虛函數(shù)的方法去重寫(xiě)Finalize方法。你只能使用像C++的析構(gòu)函數(shù)的語(yǔ)法去編寫(xiě),編譯器會(huì)自動(dòng)對(duì)你的代碼進(jìn)行一些改動(dòng)來(lái)實(shí)現(xiàn)override終結(jié)器方法。(具體可查看MSDN中析構(gòu)函數(shù)一節(jié))
~MyObject()
{
//we're being finalized (i.e.destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning:subtle bug! Keep reading!
}
但是這有一個(gè)Bug。
試想一下,你的GC是在后臺(tái)線程調(diào)用,你對(duì)GC的調(diào)用時(shí)間和對(duì)垃圾對(duì)象的釋放順序根本無(wú)法掌控,很有可能的發(fā)生的是,你的對(duì)象的某些托管資源已經(jīng)在調(diào)用終結(jié)器之前就被釋放了,當(dāng)GC調(diào)用終結(jié)器時(shí),最終會(huì)釋放兩次托管資源!
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection !=null)
{
this.databaseConnection.Dispose(); //<-- crash, GC already destroyedit
this.databaseConnection =null;
}
if (this.frameBufferImage !=null)
{
this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
所以你需要做的是讓終結(jié)器告訴Dispose方法,它不應(yīng)該再次釋放任何托管資源了(因?yàn)檫@些資源很有可能已經(jīng)被釋放了!)
標(biāo)準(zhǔn)的Dispose模式是讓Dispose函數(shù)和Finalize方法通過(guò)調(diào)用第三種方法,這個(gè)方法有一個(gè)bool類(lèi)型的形參來(lái)表示此方法的調(diào)用是通過(guò)Dispose還是GC調(diào)用終結(jié)器。在這個(gè)方法里實(shí)現(xiàn)具體的釋放資源代碼。
這個(gè)第三種方法的命名當(dāng)然可以隨意命名,但是規(guī)范的方法簽名是:
protected void Dispose(Boolean disposing)
當(dāng)然,這個(gè)參數(shù)的名字會(huì)讓人讀不懂什么意思。(很多網(wǎng)上教程都使用這個(gè)名字,第一次看很難看懂這個(gè)變量的作用)
這個(gè)參數(shù)也許可以這樣寫(xiě)…
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, butonly if I'm being called from Dispose
//(If I'm being called fromFinalize then the objects might not exist
//anymore
if(itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection !=null)
{
this.databaseConnection.Dispose();
this.databaseConnection =null;
}
if (this.frameBufferImage !=null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage =null;
}
}
}
這樣IDisposable中的Dispose方法就變成了這樣:
public void Dispose()
{
Dispose(true); //I am calling youfrom Dispose, it's safe
}
終結(jié)器就變成了這樣…
~MyObject()
{
Dispose(false); //I am *not*calling you from Dispose, it's *not* safe
}
注意:你的類(lèi)如果是從另一個(gè)類(lèi)繼承而來(lái),那么你不要忘記去調(diào)用父類(lèi)的Dispose方法
public Dispose()
{
try
{
Dispose(true); //true: safeto free managed resources
}
finally
{
base.Dispose();
}
}
所有的一切看起來(lái)都已經(jīng)很好了,除非,你想做的更好!
2.5、最完美的方法?
如果使用者手動(dòng)調(diào)用了Dispose方法,這樣,一切都被清空了。之后呢,因?yàn)槟阒貙?xiě)了Finalize方法,GC一定調(diào)用這個(gè)方法,它將會(huì)再一次調(diào)用Dispose方法!
這不僅是一種性能上的浪費(fèi),而且關(guān)鍵是在Dispose方法調(diào)用后,那些引用已經(jīng)變成了垃圾,GC會(huì)調(diào)用這些垃圾引用!
解決的方法是通過(guò)在Dispose方法中調(diào)用GC.SuppressFinalize() 來(lái)阻止GC去調(diào)用Finalize方法
public void Dispose()
{
Dispose(true); //I am calling youfrom Dispose, it's safe
GC.SuppressFinalize(this); //Hey,GC: don't bother calling finalize later
}
這樣,每一件事都照顧到了!
(注:其實(shí)可以將Dispose(bool disposing)方法變成虛函數(shù),如果你的類(lèi)被繼承)
至此,我們一步一步實(shí)現(xiàn)了最好的IDisposable方法,現(xiàn)在回頭去看看一開(kāi)始的實(shí)現(xiàn)IDisposable接口教程,是不是一切的透徹了?
三、使用終結(jié)器還是Dispose方法釋放非托管資源?
其實(shí)兩種方法都可以,但是就像在一開(kāi)始提到的,GC的垃圾回收時(shí)間不確定,對(duì)于那些你已經(jīng)不需要的資源,還是盡快釋放比較好,不應(yīng)該總等著GC的垃圾回收,而且還有一個(gè)好處是,降低GC垃圾回收的時(shí)間,提高效率。何樂(lè)而不為呢?
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Unity UGUI的ContentSizeFitter內(nèi)容尺寸適應(yīng)器組件使用示例
這篇文章主要為大家介紹了Unity UGUI的ContentSizeFitter內(nèi)容尺寸適應(yīng)器組件使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
C# VTK 移動(dòng)旋轉(zhuǎn)交互功能實(shí)現(xiàn)
對(duì)vtk場(chǎng)景中一個(gè)或多個(gè)選中物體進(jìn)行移動(dòng)旋轉(zhuǎn),今天通過(guò)本文給大家分享C# VTK 移動(dòng)旋轉(zhuǎn)交互功能實(shí)現(xiàn),感興趣的朋友跟隨小編一起看看吧2024-06-06
C#獲取某路徑文件夾中全部圖片或其它指定格式的文件名的實(shí)例方法
在本篇文章里小編給大家整理的是關(guān)于C#獲取某路徑文件夾中全部圖片或其它指定格式的文件名的實(shí)例方法,需要的朋友們參考下。2019-10-10
使用C#?11的靜態(tài)接口方法改進(jìn)?面向約定?的設(shè)計(jì)方法
我們知道接口是針對(duì)契約的定義,但是一直以來(lái)它只能定義一組“實(shí)例”的契約,而不能定義類(lèi)型的契約,因?yàn)槎x在接口中的方法只能是實(shí)例方,這篇文章主要介紹了使用C#?11的靜態(tài)接口方法改進(jìn)面向約定?的設(shè)計(jì),需要的朋友可以參考下2022-12-12
C#中Dictionary與List的用法區(qū)別以及聯(lián)系詳解
List和Dictionary想必是我們平常用到最多的C#容器了,他們使用起來(lái)都很簡(jiǎn)單,這篇文章主要給大家介紹了關(guān)于C#中Dictionary與List的用法區(qū)別以及聯(lián)系的相關(guān)資料,需要的朋友可以參考下2023-11-11

