[C#]Effective C# 條款十八:實現標準Dispose模式

[C#]Effective C# 條款十八:實現標準Dispose模式

IDisposable接口為.Net程式釋放非託管資源的標準解決方案,可為類別提供即時釋放資源的機制。


其內含有Dispose方法,為資源釋放動作的本體,在該方法的實作上需注意到需內含下面幾個重點任務:

1.判斷是否做過釋放動作
因為透過Dispose做資源的釋放後,資源仍舊會有短時間會滯留在記憶體中。此時若該物件仍具有強引用參考,垃圾收集器會無法對該物件做回收的動作。因此該物件有可能會因此再次被錯用 ,重覆釋放到已經釋放的資源,故需在該方法實作時做些檢查的動作。


    public class MyResource: IDisposable
    {
        private bool disposed = false;
        ...
        protected virtual void Dispose(bool disposing)
        {           
            if(this.disposed)
                return;
           ...
                disposed = true;
        }
      }  


2.釋放托管資源

釋放物件中的託管資源。這部分包含釋放有實作IDisposable接口的類別成員,以及斷開有繫結的事件。


3.釋放非托管資源

釋放物件中的非託管資源。


4.取消解構子調用

如我們所了解的,具有解構子的物件其在被垃圾收集器回收處理時,會先被放入解構佇列之中,再交由另一個專門處理解構動作的執行緒去做解構的動作,當解構的動作完成,該物件又會被放回原來的佇列等待垃圾收集器的回收,因此其性能上的耗費會比沒有解構子的物件還來的多。由於IDisposable在實作上會習慣加入解構子做為保險措施,防止類別的使用者忘記叫用Dispose方法,造成資源的洩漏。故在釋放完資源後,我們應該隨即在後呼叫GC.SuppressFinalize,告知垃圾收集器該物件的解構動作跳過不處理。像是:


        ...
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            ...
        }

         ~MyResource()
        {
            Dispose(false);
        }
        ...

 

完整的實作範例如下(摘至MSDN):


using System;
using System.ComponentModel;
// The following example demonstrates how to create// a resource class that implements the IDisposable interface// and the IDisposable.Dispose method.public class DisposeExample
{
    // A base class that implements IDisposable.
    // By implementing IDisposable, you are announcing that
    // instances of this type allocate scarce resources.
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource.
        private IntPtr handle;
        // Other managed resource this class uses.
        private Component component = new Component();
        // Track whether Dispose has been called.
        private bool disposed = false;
        // The class constructor.
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }
        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }
        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }
                // Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;
                // Note disposing has been done.
                disposed = true;
            }
        }
        // Use interop to call the method necessary
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);
        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method
        // does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create
        // and use the MyResource object.
    }
}

 

這邊要再特別注意的是,在建立衍生類別時,若其基底類別實作有IDisposable介面,衍生類別也需隨之實作IDisposable介面。

 

另外若在實現衍生類別的同時,有對Dispose方法或解構子覆寫的需求,務必要調用對應的基類方法,以確保基類資源能被徹底釋放。