A Better IDisposable Pattern

In .NET, objects that implement the IDisposable interface are objects that need special cleanup code to run deterministically when they're not needed anymore. (Another interesting use of this interface is to delimit a scope, but I'll focus on the original cleanup use case in this post.)

public interface IDisposable {
  // cleanup resources
  void Dispose();
}

Most .NET languages have a special syntax to automatically call the method Dispose at the end of a scope where the object is used. In C#, this special syntax is implemented with the using statement:

using (var stream = File.OpenRead("interestingFile")) { 
  // do something interesting
}

The compiler expands this code to:

var stream = File.OpenRead("interestingFile");
try { 
  // do something interesting
}
finally {
  if (stream != null) {
    ((IDisposable)stream).Dispose();
  }
}

Sweet sugar indeed.

To implement the IDisposable interface in a non-sealed class, there's an official pattern that most people take for granted (it's better described here). It takes into account that the class (or any of its subclasses) may have managed or unmanaged resources to cleanup, and that the cleanup code has to run even if the user doesn't call Dispose (either explicitly or implicitly through the using statement). In its basic form, this is what it looks like:

public class DisposableClass : IDisposable {

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

  protected virtual void Dispose(bool disposing) {
    if (disposing) {
      // Cleanup all managed resources
    }

    // Cleanup all native resources
  }

  ~DisposableClass() {
    Dispose(false);
  }
  
}

The cleanup code is in the method Dispose(bool), which is shared by Dispose() and the Finalizer (the C# destructor). If the user calls Dispose(), then Dispose(true) is called and the object's finalization is suppressed. If the user forgets to call Dispose(), the Finalizer is called when the Garbage Collector runs, which will call Dispose(false). When the Finalizer runs, it's important not to clean up managed resources, because they might have already been collected.

Dispose(bool) has a strange signature, and a misleading parameter name. What does it mean to call Dispose(bool) with disposing = true? Well, we're already calling a method named Dispose, but maybe we haven't been explicit enough about it, so we'd better set disposing to true. Or, maybe we want to dispose just a little, so let's call Dispose with disposing set to false. To avoid the confusion, let's fix it by changing some names:

  protected virtual void DisposeNativeResources(bool alsoDisposeManagedResources) {
    if (alsoDisposeManagedResources) {
      // Cleanup all managed resources
    }

    // Cleanup all native resources
  }

That's arguably better. At least the names explain what the method does and how the setting of the parameter affects its operation. But changing the names like this exposed a deeper problem. Maybe this method is trying to do too much. We should have known that already as evidenced by the boolean parameter.

By breaking this method apart, a better pattern emerges:

public class BetterDisposableClass : IDisposable {

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

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

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

UPDATE: but, we can still do better...

Comments

  1. In C++ destructor is called deterministically at the deletion of the object

    ReplyDelete

Post a Comment

Popular posts from this blog

The Acyclic Visitor Pattern

Some OO Design

NRoles: An experiment with roles in C#