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