託管,非託管資源及資源釋放

1.資源

  • 非託管資源是指:所有的Window內核對象(句柄)都是非託管資源,如對於Stream,數據庫連接,GDI+的相關對象,還有Com對象等等,這些資源並不是受到CLR管理;需要顯式釋放的,也即需要你寫代碼釋放;
  • 託管資源是指:由CLR管理分配和釋放的資源,即由CLR裏new出來的對象。並不需要顯式釋放,但是如果引用類型本身含有非託管資源,則需要進行現實釋放;

2.顯式釋放資源

  • 實現IDisposable接口的Dispose方法;
  • 析構方法(終結器);
  •  不由C#語法支持,但是約定支持的顯式釋放是:

  • 提供顯示釋放方法,比如常用的Close方法;

3.Dispose、Close和析構方法異同點

但是,還需要區分這3種方式的異同點。首先,你無法調用析構方法。析構方法是由垃圾回收機制進行調用的。換句話來說,就是你不知道析構方法被調用的時機。嚴格意義上來說,它只是作爲資源釋放的一個補救措施。

資源釋放的一個正確的措施是爲類型實現IDisposable接口的Dispose。當你需要釋放類型的資源的時候,應該顯示的調用Dipose方法。當然,這裏還有一個C#的語法糖,就是使用using程序塊,在離開using程序塊的時候,CLR會自動調用類型所創建對象的Dipose方法。

可能有人會問道,既然可以通過Dispose方法的方式來進行資源的釋放,爲什麼有些類型還需要提供一個Close方法。這裏面的區別,或者說約定在於,如果你仔細觀察這些類型:他們基本都只公開了Close方法,他們都實現了IDisposable,但都隱藏了Dispose方法。



-----------------原文--------------------------------------------------


需要明確一下C#程序(或者說.NET)中的資源。簡單的說來,C#中的每一個類型都代表一種資源,而資源又分爲兩類:

託管資源:由CLR管理分配和釋放的資源,即由CLR裏new出來的對象;

非託管資源:不受CLR管理的對象,windows內核對象,如文件、數據庫連接、套接字、COM對象等;

毫無例外地,如果我們的類型使用到了非託管資源,或者需要顯式釋放的託管資源,那麼,就需要讓類型繼承接口IDisposable。這相當於是告訴調用者,該類型是需要顯式釋放資源的,你需要調用我的Dispose方法。

不過,這一切並不這麼簡單,一個標準的繼承了IDisposable接口的類型應該像下面這樣去實現。這種實現我們稱之爲Dispose模式:

複製代碼
publicclass SampleClass : IDisposable
{
//演示創建一個非託管資源
private IntPtr nativeResource = Marshal.AllocHGlobal(100);
//演示創建一個託管資源
private AnotherResource managedResource =new AnotherResource();
privatebool disposed =false;

///<summary>
/// 實現IDisposable中的Dispose方法
///</summary>
publicvoid Dispose()
{
//必須爲true
Dispose(true);
//通知垃圾回收機制不再調用終結器(析構器)
GC.SuppressFinalize(this);
}

///<summary>
/// 不是必要的,提供一個Close方法僅僅是爲了更符合其他語言(如C++)的規範
///</summary>
publicvoid Close()
{
Dispose();
}

///<summary>
/// 必須,以備程序員忘記了顯式調用Dispose方法
///</summary>
~SampleClass()
{
//必須爲false
Dispose(false);
}

///<summary>
/// 非密封類修飾用protected virtual
/// 密封類修飾用private
///</summary>
///<param name="disposing"></param>
protectedvirtualvoid Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
// 清理託管資源
if (managedResource !=null)
{
managedResource.Dispose();
managedResource 
=null;
}
}
// 清理非託管資源
if (nativeResource != IntPtr.Zero)
{
Marshal.FreeHGlobal(nativeResource);
nativeResource 
= IntPtr.Zero;
}
//讓類型知道自己已經被釋放
disposed =true;
}

publicvoid SamplePublicMethod()
{
if (disposed)
{
thrownew ObjectDisposedException("SampleClass""SampleClass is disposed");
}
//省略
}
}
複製代碼

在Dispose模式中,幾乎每一行都有特殊的含義。

在標準的Dispose模式中,我們注意到一個以~開頭的方法:

複製代碼
///<summary>
/// 必須,以備程序員忘記了顯式調用Dispose方法
///</summary>
~SampleClass()
{
//必須爲false
Dispose(false);
}
複製代碼

這個方法叫做類型的終結器。提供終結器的全部意義在於:我們不能奢望類型的調用者肯定會主動調用Dispose方法,基於終結器會被垃圾回收器調用這個特點,終結器被用做資源釋放的補救措施。

一個類型的Dispose方法應該允許被多次調用而不拋異常。鑑於這個原因,類型內部維護了一個私有的布爾型變量disposed:

        private bool disposed = false;

在實際處理代碼清理的方法中,加入瞭如下的判斷語句:

if (disposed)
{
return;
}
//省略清理部分的代碼,並在方法的最後爲disposed賦值爲true
disposed =true;

這意味着類型如果被清理過一次,則清理工作將不再進行。

應該注意到:在標準的Dispose模式中,真正實現IDisposable接口的Dispose方法,並沒有實際的清理工作,它實際調用的是下面這個帶布爾參數的受保護的虛方法:

複製代碼
///<summary>
/// 非密封類修飾用protected virtual
/// 密封類修飾用private
///</summary>
///<param name="disposing"></param>
protectedvirtualvoid Dispose(bool disposing)
{
//省略代碼
}
複製代碼

之所以提供這樣一個受保護的虛方法,是爲了考慮到這個類型會被其他類繼承的情況。如果類型存在一個子類,子類也許會實現自己的Dispose模式。受保護的虛方法用來提醒子類必須在實現自己的清理方法的時候注意到父類的清理工作,即子類需要在自己的釋放方法中調用base.Dispose方法。

還有,我們應該已經注意到了真正撰寫資源釋放代碼的那個虛方法是帶有一個布爾參數的。之所以提供這個參數,是因爲我們在資源釋放時要區別對待託管資源和非託管資源。

在供調用者調用的顯式釋放資源的無參Dispose方法中,調用參數是true:

publicvoid Dispose()
{
//必須爲true
Dispose(true);
//其他省略
}

這表明,這個時候代碼要同時處理託管資源和非託管資源。

在供垃圾回收器調用的隱式清理資源的終結器中,調用參數是false:

~SampleClass()
{
//必須爲false
Dispose(false);
}

這表明,隱式清理時,只要處理非託管資源就可以了。

那麼,爲什麼要區別對待託管資源和非託管資源。在認真闡述這個問題之前,我們需要首先弄明白:託管資源需要手動清理嗎?不妨先將C#中的類型分爲兩類,一類繼承了IDisposable接口,一類則沒有繼承。前者,我們暫時稱之爲非普通類型,後者我們稱之爲普通類型。非普通類型因爲包含非託管資源,所以它需要繼承IDisposable接口,但是,這個包含非託管資源的類型本身,它是一個託管資源。所以說,託管資源需要手動清理嗎?這個問題的答案是:託管資源中的普通類型,不需要手動清理,而非普通類型,是需要手動清理的(即調用Dispose方法)。

Dispose模式設計的思路基於:如果調用者顯式調用了Dispose方法,那麼類型就該按部就班爲自己的所以資源全部釋放掉。如果調用者忘記調用Dispose方法,那麼類型就假定自己的所有託管資源(哪怕是那些上段中闡述的非普通類型)全部交給垃圾回收器去回收,而不進行手工清理。理解了這一點,我們就理解了爲什麼Dispose方法中,虛方法傳入的參數是true,而終結器中,虛方法傳入的參數是false。

 

注意:我們提到了需要及時釋放資源,卻並沒有進一步細說是否需要及時讓引用等於null這一點。有一些人認爲等於null可以幫助垃圾回收機制早點發現並標識對象是垃圾。其他人則認爲這沒有任何幫助。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章