Thursday, 6 September 2012

Finalization Pattern

If you object uses unmanaged resources then you must implement a Finalizer. A Finalizer looks very much like a C++ Destructor and is called by the finalization thread some point after the object has been garbage collected.

Therefore objects that contain native types must :-
  • Implement a Finalizer
  • Implement IDisposable
  • These methods should delegate so a common private/protected method
The aim of implementing IDisposable is to allow the resources to be reclaimed eagerly in a deterministic manner.

Where possible do not rely on finalization, always explicitly call Dispose().


public class MyNativeWrapper : IDiposable  
{  
    protected bool isDisposed;  

    // These methods should not throw
    protected virtual void CleanUpManagedResources() { 
        // ...

        // If in an inheritance hierarchy, chain to base
        //base.CleanUpManagedResources();
    }

    // These methods should not throw
    protected virtual void CleanUpNativeResources() {
        // ...

        // If in an inheritance hierarchy, chain to base
        //base.CleanUpNativeResources();
    }

    public void Dispose()  
    {  
        if (!isDisposed)
            CleanUpManagedResources();

        isDisposed = true;

        CleanUpNativeResources()     

        GC.SuppressFinalize(this);
    }  

    // Only write Finalizer if class owns unmanaged resources
    ~File()  
    {  
        CleanUpNativeResources();  
    }  
}  

If we explicitly dispose the object using Dispose() then we can free all unmanaged resources eagerly and suppress finalization as the resources are already freed before garbage collection and finalization.

Objects with finalizers must undergo Finalization on the finalization thread, therefore they are more expensive to destroy. The finalization thread is non deterministic, you cannot predict when your object will undergo finalization, therefore objects with finalizers will live longer than the same object without a finalizer. You should therefore only declare a finalizer if you need one.

Your finalizers must be thread-safe and finish in a timely manner as they will be called from the finalization thread and there is only one of these.

A better approach in many instances is to use a RAII pattern and use SafeHandle to wrap the unmanaged resource. You therefore will not need to write Dispose(), just call it.

public class ManagedDisposable : IDisposable {
    private IDisposable _otherDisposable;  

    // ...

    public void Dispose() {  
        _otherDisposable.Dispose();  
    }  
}  

...

using (var myObject = new ManagedDisposable()) {
   ...

}

The using(...) {}  pattern completes the RAII approach by ensuring finalizer(s) get called eagerly when the scope of the using block ends.

No comments:

Post a Comment