釋放未托管的資源有兩種方法
1、析構函數
2、實現System.IDisposable接口
一、析構函數
構造函數可以指定必須在創建類的實例時進行的某些操作,在垃圾收集器刪除對象時,也可以調用析構函數。析構函數初看起來似乎是放置釋放未托管資源、執行一般清理操作的代碼的最佳地方。但是,事情並不是如此簡單。由於垃圾回收器的運行規則決定了,不能在析構函數中放置需要在某一時刻運行的代碼,如果對象占用了寶貴而重要的資源,應盡可能快地釋放這些資源,此時就不能等待垃圾收集器來釋放了.
實例
C# 代碼 復制using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MemRelease
{ class Program { ~Program() { // Orders. } static void Main(string[] args) { } } }
在IL DASM中,你會發現並沒有這個析構的方法。C#編譯器在編譯析構函數時,會隱式地把析構函數的代碼編譯為Finalize()方法的對應代碼,確保執行父類的Finalize()方法 看下這段代碼中對於析構函數的編譯:
C# 代碼 復制.method family hidebysig virtual instance void
Finalize() cil managed
{ // Code size 14 (0xe) .maxstack 1 .try { IL_0000: nop IL_0001: nop IL_0002: leave.s IL_000c } // end .try finally { IL_0004: ldarg.0 IL_0005: call instance void [mscorlib]System.Object::Finalize() IL_000a: nop IL_000b: endfinally } // end handler IL_000c: nop IL_000d: ret } // end of method Program::Finalize
使用析構函數來釋放資源有幾個問題:
1、與C++析構函數相比,C#析構函數的問題是他們的不確定性。在刪除C++對象時,其析構函數會立即執行,但是由於垃圾收集器的工作方式,無法確定C#對象的析構函數何時執行。
2、C#析構函數的執行會延遲對象最終從內存中刪除的時間。有析構函數的對象需要2次處理才能刪除:第一次調用析構函數時,沒有刪除對象,第二次調用才真正刪除對象。
二、IDisposable接口
IDisposable接口定義了一個模式,為釋放未托管的資源提供了確定的機制,並避免產生析構函數固有的與垃圾函數器相關的問題。IDisposable接口聲明了一個方法Dispose(),它不帶參數,返回void。
1、MSDN建議按照下面的模式實現IDisposable接口
C# 代碼 復制public class Foo: IDisposable
{ public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!m_disposed) { if (disposing) { // Release managed resources } // Release unmanaged resources m_disposed = true; } } ~Foo() { Dispose(false); } private bool m_disposed; }
在.NET的對象中實際上有兩個用於釋放資源的函數:Dispose和Finalize
(1)、Finalize的目的是用於釋放非托管的資源,而Dispose是用於釋放所有資源,包括托管的和非托管的
(2)、void Dispose(bool disposing)函數通過一個disposing參數來區別當前是否是被Dispose()調用
如果是被Dispose()調用,那麼需要同時釋放托管和非托管的資源。如果是被~Foo()(也就是C#的Finalize())調用了,那麼只需要釋放非托管的資源即可。
(3)、Dispose()函數是被其它代碼顯式調用並要求釋放資源的,而Finalize是被GC調用的
在GC調用的時候Foo所引用的其它托管對象可能還不需要被銷毀,並且即使要銷毀,也會由GC來調用。因此在Finalize中只需要釋放非托管資源即可。另外一方面,由於在Dispose()中已經釋放了托管和非托管的資源,因此在對象被GC回收時再次調用Finalize是沒有必要的,所以在Dispose()中調用GC.SuppressFinalize(this)避免重復調用Finalize。
然而,即使重復調用Finalize和Dispose也是不存在問題的,因為有變量m_disposed的存在,資源只會被釋放一次,多余的調用會被忽略過去。
Finalize、Dispose保證了
(1)、 Finalize只釋放非托管資源;
(2)、 Dispose釋放托管和非托管資源;
(3)、 重復調用Finalize和Dispose是沒有問題的;
(4)、 Finalize和Dispose共享相同的資源釋放策略,因此他們之間也是沒有沖突的。
2、IDisposable例子
C# 代碼 復制
namespace 資源回收
{ class Program { static void Main(string[] args) { //使用using對實現IDisposable的類了進行資源管理 /*拿到一個對象的時候,首先判斷這個對象是否實現了IDisposable接口,如果實現了,最好就用using包裹住這個對象,保證這個對象用完之後被釋放掉,否則很可能出現資源洩露的問題 */ using (Telphone t1 = new Telphone()) { t1.Open(); t1.Speak("hello"); t1.Bomb(); //t1.Dispose();//如果在這裡調用了Dispose()方法釋放資源,那麼在執行t1.Open()方法就出錯,電話線已經被剪斷了,無法再打電話了 t1.Open(); t1.Speak("I am back!"); }//代碼執行到這裡後,就會調用Dispose方法來進行資源回收 Console.ReadKey(); } } /// <summary> /// Telphone類實現了IDisposable接口 /// </summary> class Telphone : IDisposable { /// <summary> /// 電話狀態 /// </summary> private TelphoneState state; /// <summary> /// 打電話 /// </summary> public void Open() { if (state == TelphoneState.Disposed) { throw new Exception("電話線已經被剪斷,無法打開!"); } state = TelphoneState.Open; Console.WriteLine("拿起電話"); } /// <summary> /// 說話 /// </summary> /// <param name="s">說話內容</param> public void Speak(string s) { if (state != TelphoneState.Open) { throw new Exception("沒有連接"); } Console.WriteLine(s); } /// <summary> /// 掛掉電話 /// </summary> public void Bomb() { state = TelphoneState.Close; Console.WriteLine("掛掉電話"); } #region IDisposable 成員 /// <summary> /// 實現IDisposable接口中的Dispose()方法來釋放非托管資源 /// 如何釋放非托管資源由程序自己定 /// </summary> public void Dispose() { if (state == TelphoneState.Open) { Bomb();//掛掉電話 } state = TelphoneState.Disposed; Console.WriteLine("剪斷電話線"); } #endregion } /// <summary> /// 電話狀態枚舉 /// </summary> enum TelphoneState { Open, Close, Disposed } }
程序運行結果:
三、析構函數和IDisposable混合調用的例子
C# 代碼 復制
public class ResourceHolder : IDisposable
{ private bool isDispose = false; // 顯示調用的Dispose方法 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } // 實際的清除方法 protected virtual void Dispose(bool disposing) { if (!isDisposed) { if (disposing) { // 這裡執行清除托管對象的操作. } // 這裡執行清除非托管對象的操作 } isDisposed=true; } // 析構函數 ~ResourceHolder() { Dispose (false); } }
托管資源和非托管資源這個要從內存回收上來講,首先一個點很明確:托管資源,.net垃圾回收器自動回收,非托管資源,垃圾回收期沒法自動回收;
另外一點,托管資源由.net核心管理創建,非托管資源是由.net核心調用其他的接口創建,.net無法控制,例如active控件,畫筆、畫刷。這些都是.Net調用系統接口創建的,它管不著,只能由用戶自已釋放。
最後,.Net內核封裝了的,能夠或者有權限自動釋放的就是托管資源;如果是.Net調用外部資源,無法自動釋放的就是非托管資源。
Timer是托管對象,Dispsoe方法可以手動調用,但更好的方法是用using塊調用,這個可以避免由於異常導致dipose沒執行到。
如果是COM對象要在dispose中調用Marshal.ReleaseComObject(comobj);確保COM對象被正確回收。