[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方法或解構子覆寫的需求,務必要調用對應的基類方法,以確保基類資源能被徹底釋放。