Disposable objects

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

  1. GC執行時,檢測到一個不被使用的物件時,會檢查該類別是否有Finalize方法,如果存在Finalize方法,會把指向該物件的參考位址存在等待解構表中,並且該物件執行個體能會被視為使用中。

  2. CLR將有一個單獨的執行緒,負責處理等待解構表,其方法就是依序透過參考位址,呼叫等待解構表裡每個物件Finalize方法,然後刪除參考位址,這時託管累堆中的物件執行個體將處於不再被使用的狀態。

  3. 下一個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/

http://stackoverflow.com/questions/3368802/what-is-the-difference-in-managed-and-unmanaged-code-memory-and-size

http://blog.miniasp.com/post/2009/10/12/About-CSharp-using-Statement-misunderstanding-on-try-catch-finally.aspx

 

 

一天一分享,身體好健康。

該追究的不是過去的原因,而是現在的目的。