這兩天幫助其它項目組Review代碼發現有些地方實現了IDispose接口同時也發現了一些關於IDispose的問題:
A類型實現了IDispose接口B類型裡面含有A類型的字段B類型沒有實現IDispose接口
一個類裡面實現了Finalize終結器同時也實現了IDispose接口但在Dispose方法裡面沒有調用GCSuppressFinalize(this)方法
下面我對以上兩個問題分別分析一下並提出解決方案
問題:如果A類型裡面有非托管資源需要在實現的IDispose接口裡面釋放由於B類型沒有實現IDispose接口B類型的使用者要想釋放A類型的非托管資源並不方便這樣的話就有可能忘記了釋放A類型的非托管資源
解決方案:
實現B類型的IDispose接口在Dispose方法裡面調用A類型的Dispose方法這樣B類型的使用者在調用B類型Dispose的同時就把A類型的Dispose也調用了
問題:在Dispose方法裡面沒有調用GCSuppressFinalize(this)方法會有什麼問題呢這樣會導致垃圾回收器不能對 這個類型的對象及時回收 當GC開始工作的時候它首先將沒有終結器的垃圾對象從內存中移除有終結器的所有對象則添加到一個垃圾隊列當中GC會調用一個新線程來執行這些對象的 終結器當終結器執行完畢後這個對象會從隊列中被移除這個對象在隊列中移除之後當GC再次開始工作的時候這個對象才能夠被回收所以有終結器的對 象會比沒有的在內存中保留更長的時間在後面我會對這裡再詳細的描述一下
解決方案:
在Dispose方法中調用GCSuppressFinalize(this)方法這樣的話就不會把有終結器的對象則添加到垃圾隊列當中
切入正題
net中非托管代碼清理有兩種方式:Finalize方式和Dispose方式
Finalize方式:通過對自定義類型實現一個Finalize方法來釋放非通過資源
從net開始C#編譯器不能對Finalize進行顯示的調用和重寫必須使用析構函數來實現它
class A
{
~A()
{
釋放資源;
}
}
上面的代碼就是通過Finalize方式來釋放資源的跟C++用析構函數釋放資源的代碼很象
但是它實現方式和C++不同因為它是由垃圾回收器來管理內存的
大家看到了用Finalize方式釋放非托管資源很簡單但是如果你了解了他的實現方式你可能就不會選擇用它來釋放非托管資源
那Finalize方式在net內部是如何實現的呢?
當GC(垃圾回收器)開始工作的時候它首先將沒有終結器的垃圾對象從內存中移除有終結器的所有對象則添加到一個終止化隊列當中GC會調用一個 新線程來執行這些對象的終結器當終結器執行完畢後這些對象會從隊列中被移除這時候由於這些對象在第一次檢測到的時候沒有被釋放它們將會進入第代 對象直到GC檢測到第代對象和第代對象再次充滿時這時候GC才會把剛才那些對象釋放掉所以有終結器的對象會比沒有的在內存中保留更長的時間
提示:垃圾回收器把托管堆中的對象分為代分別是一般分配為:代約K代約是MB第代約是MB代齡越高容量就越 大顯然效率也就越低首先被添加到托管堆中的對象被定為第代當第代充滿時就會執行垃圾回收未被回收的對象代領將提升代
由於以上原因應該避免僅使用Finalize方式釋放非托管資源
Dispose模式:在自定義類中實現IDispose接口在接口中的Dispose方法中對非托管資源進行釋放閒話少說上代碼
public class MyResourceRelease: IDisposable
{
/// 保證資源只用釋放一次
private bool _alreadyDisposed = false;
/// 用來判斷釋放資源的類別(托管和非托管)
protected virtual void Dispose(bool isDisposing)
{
if(_alreadyDisposed)
{
return;
}
if(isDisposing)
{
//釋放托管資源
}
//釋放非托管資源
_alreadyDisposed = true;
}
public void Dispose()
{
Dispose(true);
}
}
上面的代碼就是用Dispose方式釋放資源的方法因為上面自定義的Dispose(bool isDisposing)方法是virtual的所以還可以在派生類裡面對它進行override
[] []
From:http://tw.wingwit.com/Article/program/net/201311/15535.html