Chapter 2 - Item 17 : Implement the Standard Dispose Pattern

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

針對 unmanaged resources,本章節提供了一個標準的設計模式:IDisposable 介面與 Finalizer。
此模式能夠讓繼承的子類別也能夠實作自身的清除資源邏輯,當類別中有非託管資源時,可善加利用。

IDisposable 介面:

public interface IDisposable
{
    void Dispose( );
}

Dipose( ) 方法應該包含以下四個任務:

1. 清除所有非託管資源。
2. 清除所有託管資源(包含事件連結)。
3. 用旗標記錄該物件是否已執行過 Dipose,若在已執行 Dispose 的物件執行任何方法應該擲出例外(ObjectDisposedException)。
4. 呼叫 GC.SuppressFinalize( this ),通知 GC 此物件已不需執行 Finalizer。

為了讓繼承體系中的所有類別都能夠確實清除資源;需要加入一些設計巧思。

1. 利用 protected virtual void dipose( bool isDisposing )讓各類別實作各自的清除資源邏輯。

範例程式:

public class MyResourceHog : IDisposable
{
    // Flag for already disposed.
    private bool _isDisposed;

    // Implementation of IDisposable.
    // Call the virtual dispose method.
    // Suppress Finalization.
    public void Dispose( )
    {
        dispose( true );
        GC.SuppressFinalize( this );
    }

    public void M1( )
    {
        if ( _isDisposed )
            throw new ObjectDisposedException( "MyResourceHog",
                "Called M1 method on disposed object." );

        // Do some work.
    }

    // Virtual dispose method.
    protected virtual void dispose( bool isDisposing )
    {
        if ( _isDisposed )
            return;

        if ( isDisposing )
        {
            // Free managed resources.
        }

        // Free unmanaged resources.

        // Set _isDisposed = true.
        _isDisposed = true;
    }

    // Add Finalizer only if this class directly contains
    // unmanaged resources.
    ~MyResourceHog( )
    {
        dispose( false );
    }
}

public class DerivedResourceHog : MyResourceHog
{
    // Have its own disposed flag.
    private bool _isDisposed;

    protected override void dispose( bool isDisposing )
    {
        // Don't dispose more than once.
        if ( _isDisposed )
            return;

        if ( isDisposing )
        {
            // Free managed resourced here.
        }

        // Free unmanaged resourced here.

        // Let the base class free its resources.
        // Base class is responsible for calling
        // GC.SuppressFinalize( )
        base.dispose( isDisposing );

        // Set Derived class disposed flag.
        _isDisposed = true;
    }

    // Add Finalizer only if this class directly contains
    // unmanaged resources.
    ~DerivedResourceHog( )
    {
        dispose( false );
    }
}

這裡有幾個重點:

• _isDisposed 旗標為各類別各自擁有,確保該類別是否已 Disposed 的正確性。

• Finalizer 視為一種保護機制,未正常呼叫 Dispose 時;仍會由 GC 加入 Freachable Queue 非同步執行(增加系統資源消耗)。

• Finalizer 不一定需要加入,端看該類別有無 unmanaged resources 需要釋放。

2. Finalizer 只做資源釋放,不應加入其他無關的操作。

public class BadClass
{
    // Store a reference to a global reference.
    public static readonly List<BadClass> _finalizedList = new List<BadClass>( );

    public BadClass( )
    {
    }

    ~BadClass( )
    {   
        // Add this object to the list.
        // This object is reachable, no longer garbage. 
        // It's back !
        _finalizedList.Add( this );
    }
}

在此例中,加入 _finalizedList 中的 BadClass 物件有可能已經不可用,原因在於該物件已經執行過 Finalizer(該物件某些資源已被釋放)。

結論:
1. 遇到 unmanaged resources 由統一的設計模式處理,增加程式碼的可維護性。

2. 仔細考慮是否加入 Finalizer;在大部分的情境下(沒有 unmanaged resources),並不需要實作 Finalizer。

參考資料:
Deep Dive Into C# - Garbage Collection And Disposal - Part One
Deep Dive Into C# - Garbage Collection And Disposal - Part Two