Revisiting IDisposable

In my last post, I've introduced a better alternative to the official pattern for implementing the IDisposable interface in .NET:

public class BetterDisposableClass : IDisposable {

  public void Dispose() {
    CleanUpManagedResources();
    CleanUpNativeResources();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

This pattern is easier to understand because it cleanly separates the responsibilities to implement a disposable class that releases unmanaged (native) and managed resources in different methods. Each method is highly focused on a single identifiable task.

This concept can be taken one step further, where different classes are created to deal with each concern. First, each unmanaged resource must be encapsulated in its own class:

public class NativeDisposable : IDisposable {

  public void Dispose() {
    CleanUpNativeResource();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

  // ...

  IntPtr _nativeResource;

}

This class encapsulates a single unmanaged resource, and needs to implement the finalizer as a last resort to release it, since unmanaged resources are not claimed by the garbage collector. All classes that need to deal with a particular unmanaged resource will do so through its encapsulating class. The result is that they won't need to be concerned with unmanaged resources anymore.

And so, classes that are composed of other disposable objects should also implement IDisposable to release them, in a very straightforward way:

public class ManagedDisposable : IDisposable {

  // ...

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

  IDisposable _otherDisposable;

}

Now Dispose is not ambiguous like in the original pattern, since it only deals with managed resources. This class is much easier to implement, as it doesn't need a finalizer and the logic to suppress it should Dispose be called.

This design properly separates the responsibilities to implement IDisposable and results in a much more robust and maintainable solution.

Comments

  1. Great post! I really like the simplicity that results from this usage pattern. Thanks for sharing.

    ReplyDelete
  2. Thank you. Great post!

    ReplyDelete
  3. Many thanks for this. I found it useful to implement as an abstract class:-

    public abstract class myDisposable : IDisposable
    {
    public void Dispose()
    {
    disposeManaged();
    disposeNative();
    GC.SuppressFinalize(this);
    }
    //---------------------------------------------------------------------------------
    protected abstract void disposeManaged();
    protected virtual void disposeNative() {}
    //---------------------------------------------------------------------------------
    ~myDisposable()
    {
    disposeNative();
    }
    }

    ReplyDelete
  4. Yes Grebe, that's a very good idea.

    ReplyDelete
  5. Very good pattern to implement Dispose. Thanks.

    ReplyDelete
  6. I think that in any case, there need to add private boolean variable "disposed" to not perform a cleanup call again and avoid an exception

    ReplyDelete
    Replies
    1. No, because when Dispose is called. The destructor will not be called anymore (it is Supressed).
      If Dispose is not called manually, then it hasn't been called yet, so it is fine to call it from destructor.

      Delete

Post a Comment

Popular posts from this blog

The Acyclic Visitor Pattern

Some OO Design

NRoles: An experiment with roles in C#