Disposable objects
.NET垃圾收集器(GC)會自動清除沒有被參考到的物件,但是僅限於managed code,而有些資源GC沒有辦法自動釋放,例如File handlers、window handlers、 network sockets、 database connections ... 等等,這些都需要手動釋放資料。 假設我們現在打開一個檔案,如果沒有將其關閉的話,則此檔案就沒辦法再其他地方被存取或修改,所以FileStream class 才有實作Dispose method,將檔案做關閉的動作。
如果我寫了一隻class,但是裡面包含unmanaged code,當別人使用我的class的時候,怎麼能保證使用class的人會去釋放這些unmanaged code的資源呢? 此時IDisposable與Finalize就是在這個時候用上。 這邊要特別注意,IDisposable與Finalize 實際上,並不會幫你自動釋放資源,只能當作是一個method,會自動幫你執行,實際上釋放資源的程式碼,還是得要程式師自己實作。 接下來就說明IDisposable與Finalize 完成一個設計模式,讓unmanaged code能夠保證被釋放。
程式範例:
我有一個Demo類別,實作IDisposable,並且在類別使用結束後,要呼叫Dispose(),釋放unmanaged resource。
Dispose
public class Demo :IDisposable
{
public void DoSomething()
{
Console.WriteLine("working");
}
public void Dispose()
{
// release unmanaged resource code
Console.WriteLine("release unmanaged resource");
}
}
static void Main(string[] args)
{
Demo d = new Demo();
try
{
d.DoSomething(); // output : working
}
finally
{
d.Dispose(); // output : release unmanaged resource
}
}
用try finally的方式,不管有沒有,丟Exception,一定會執行到,這樣就能保證一定能釋放資源。
using陳述式
上述範例實作IDisposable的原因,是因為類別只要有實作IDisposable,則可以用.NET提供另外一種using陳述式的寫法,當using陳述式結束,則會呼叫Dispose()。 效果與try finally 完全一樣。
static void Main(string[] args)
{
using (Demo d = new Demo())
{
d.DoSomething(); // output : working
}
// output : release unmanaged resource
}
以上兩種做法,只要開發者,照實寫基本上不會有太大問題,但是...總是有那幾個但是,沒寫的話,那該怎麼辦,等unmanaged resource 一直增加,讓記憶體溢位?? 當然是Finalize 登場救援
Finalize
-
GC執行時,檢測到一個不被使用的物件時,會檢查該類別是否有Finalize方法,如果存在Finalize方法,會把指向該物件的參考位址存在等待解構表中,並且該物件執行個體能會被視為使用中。
-
CLR將有一個單獨的執行緒,負責處理等待解構表,其方法就是依序透過參考位址,呼叫等待解構表裡每個物件Finalize方法,然後刪除參考位址,這時託管累堆中的物件執行個體將處於不再被使用的狀態。
-
下一個GC執行時,將釋放已被呼叫過Finalize方法的那些物件執行個體。
Finalize 用法 ~Demo
public class Demo :IDisposable
{
public void DoSomething()
{
Console.WriteLine("working");
}
public void Dispose()
{
// release unmanaged resource code
Console.WriteLine("release unmanaged resource");
}
// Finalize 解構方法
~Demo()
{
// release unmanaged resource code
Console.WriteLine("release unmanaged resource by Finalize");
}
}
static void Main(string[] args)
{
Demo d = new Demo();
d.DoSomething();// output : working
GC.Collect(); // output :release unmanaged resource by Finalize
Console.Read();
}
可以看到,就算我沒有呼叫任何,Dispose方法,當GC執行的時候,會去執行解構的方法,但是執行解構的方法,代價非常高,所以基本上是希望能在Dispose就把unmanaged resource釋放掉,不希望GC去執行解構的方法。
Advanced Dispose Pattern
IDisposable與Finalize 混用,達到最佳效果。
public class AdvancedDemo : IDisposable
{
public void Dispose()
{
this.Dispose(true);
}
protected virtual void Dispose(bool isDisposing)
{
if (isDisposing)
{
/*---------------重點---------------*/
/*告訴.NET此物件被回收時不需要呼叫Finalize方法*/
GC.SuppressFinalize(this);
}
// release unmanaged resource code
Console.WriteLine("release unmanaged resource");
}
~AdvancedDemo()
{
this.Dispose(false);
}
}
經過這樣的設計,有兩大好處
-
如果程式師有執行Dispose方法,則不會執行Finalize方法
-
如果沒有執行Dispose方法,則執行Finalize方法,保證將unmanaged resource 釋放掉
參考資料
http://www.c-sharpcorner.com/UploadFile/nityaprakash/back-to-basics-dispose-vs-finalize/
一天一分享,身體好健康。
該追究的不是過去的原因,而是現在的目的。