C#之資源回收

    (史帝芬, 2006/10/21, hi.steven@gmail.com)

        .Net framework和Java VM一樣針對一般資源會自動回收,對於 unmanaged的資源,Java會於finalize 做處理,語法上C#和C++相同,作法上則和Java相同,都是交給VM處理。事實上,C#也有個Finalize method,這個method與Java的finalize相同,於物件將被回收時會被 VM呼叫。C#的編譯器在編譯時實際上會將解構子轉換成Finalize的覆載method,如下所示。
    class MyClass
    {
    ~MyClass() { ... }
    }
    會轉換成:
    class MyClass
    {
    protected override void Finalize()
    {
    try { ... }
    finally { base.Finalize(); }
    }
    }

  • 解構子
        C#提供了與C++一樣語法的解構子,當object消失時,.Net framework 會執行解構子,我們可於此時加入程式進行資源回收,與Java相同的是,什麼時候object會消失? 這由VM決定,並非在object變成null時,VM即會立刻執行解構子並回收object。底下範例程式1.1可以看出,.Net framework 會先建構父類別再建構子類別,解構時則會以反方向,先解構子類別,再解構父類別,特別注意的是,解構子不需由程式呼叫,.Net framework會自動執行。
        如果要強迫.Net framework資源回收,可以使用如範例程式1.2的方法,以GC指令強迫.Net framework資源回收,並使用WaitForPendingFinalizers method等到確實回收後再繼續執行,因為資源回收時是在另一個thread之故。

  • * 範例程式 1.1
    using System;

    namespace Garbage
    {
    class Father
    {
    public Father()
    {
    Console.WriteLine("建構 Father");
    }

    ~Father()
    {
    Console.WriteLine("解構 Father");
    }
    }

    class Son : Father
    {
    public Son()
    {
    Console.WriteLine("建構 Son");
    }

    ~Son()
    {
    Console.WriteLine("解構 Son");
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    Console.WriteLine("程式開始");
    Son me = new Son();
    me = null;
    Console.WriteLine("程式結束");
    }
    }
    }
    * 執行結果 1.1
    程式開始
    建構 Father
    建構 Son
    程式結束
    解構 Son
    解構 Father

    * 範例程式 1.2
    using System;

    namespace Garbage
    {
    class Father
    {
    public Father()
    {
    Console.WriteLine("建構 Father");
    }

    ~Father()
    {
    Console.WriteLine("解構 Father");
    }
    }

    class Son : Father
    {
    public Son()
    {
    Console.WriteLine("建構 Son");
    }

    ~Son()
    {
    Console.WriteLine("解構 Son");
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    Console.WriteLine("程式開始");
    Son me = new Son();
    me = null;
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine("程式結束");
    }
    }
    }
    * 執行結果 1.2
    程式開始
    建構 Father
    建構 Son
    解構 Son
    解構 Father
    程式結束

  • 實作IDisposable介面
        IDisposable介面僅有一個method -- Dispose,實作IDisposable 介面後,在using中.Net framework將會自動呼叫Dispose method回收資源,如範例程式 2.1 所示。但是由執行結果2.1各位是否看出端倪? .Net framework只呼叫子類別的Dispose method 卻沒有呼叫父類別的Dispose method,所以,實作IDisposable介面時,父類別的資源回收要由子類別呼叫,請看範例程式2.2。
        範例程式 2.3是讓各位了解,實作IDisposable介面在using中.Net framework會自動呼叫Dispose method進行資源回收,但是在一般狀況下並不會。需要程式自行呼叫Dispose method或是改成如範例程式 2.4所示,其中GC.SuppressFinalize(this)是通知 .Net framework不要再對這個object進行資源回收,如果不這麼做,當主程式中自行呼叫Dispose method, .Net framework又於object消失後執行解構子,會造成Dispose method重複執行。

    * 範例程式 2.1
    using System;

    namespace Garbage2
    {
    class Father : IDisposable
    {
    public Father()
    {
    Console.WriteLine("建構 Father");
    }

    public void Dispose()
    {
    Console.WriteLine("解構 Father");
    }
    }

    class Son : Father, IDisposable
    {
    public Son()
    {
    Console.WriteLine("建構 Son");
    }

    new public void Dispose()
    {
    Console.WriteLine("解構 Son");
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    Console.WriteLine("程式開始");
    using (Son me = new Son())
    {
    Console.WriteLine("程式執行中");
    }
    Console.WriteLine("程式結束");
    }
    }
    }
    * 執行結果 2.1
    程式開始
    建構 Father
    建構 Son
    程式執行中
    解構 Son
    程式結束

    * 範例程式 2.2
    using System;

    namespace Garbage2
    {
    class Father : IDisposable
    {
    public Father()
    {
    Console.WriteLine("建構 Father");
    }

    public void Dispose()
    {
    Console.WriteLine("解構 Father");
    }
    }

    class Son : Father, IDisposable
    {
    public Son()
    {
    Console.WriteLine("建構 Son");
    }

    new public void Dispose()
    {
    Console.WriteLine("解構 Son");
    base.Dispose();
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    Console.WriteLine("程式開始");
    using (Son me = new Son())
    {
    Console.WriteLine("程式執行中");
    }
    Console.WriteLine("程式結束");
    }
    }
    }
    * 執行結果 2.2
    程式開始
    建構 Father
    建構 Son
    程式執行中
    解構 Son
    解構 Father
    程式結束

    * 範例程式 2.3 (僅有主程式,其餘請參考2.2)
        class Program
    {
    static void Main(string[] args)
    {
    Console.WriteLine("程式開始");
    Son me = new Son();
    me = null;
    Console.WriteLine("程式結束");
    }
    }
    * 執行結果 2.3
    程式開始
    建構 Father
    建構 Son
    程式結束

    * 範例程式 2.4
    using System;

    namespace Garbage2
    {
    class Father : IDisposable
    {
    public Father()
    {
    Console.WriteLine("建構 Father");
    }

    public void Dispose()
    {
    Console.WriteLine("解構 Father");
    GC.SuppressFinalize(this);
    }

    ~Father()
    {
    Dispose();
    }
    }

    class Son : Father, IDisposable
    {
    public Son()
    {
    Console.WriteLine("建構 Son");
    }

    new public void Dispose()
    {
    Console.WriteLine("解構 Son");
    GC.SuppressFinalize(this);
    }

    ~Son()
    {
    Dispose();
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    Console.WriteLine("程式開始");
    Son me = new Son();
    me = null;
    Console.WriteLine("程式結束");
    }
    }
    }
    * 執行結果 2.4
    程式開始
    建構 Father
    建構 Son
    程式結束
    解構 Son
    解構 Father