Secrets of IDisposable

We all know the Microsoft examples for IDisposable where some kind of unmanaged resource (usually a Win32 handle of a database connection) needs to be evicted in a timely manner and therefore needs to be manually disposed. In this article, I will try to clear up some common misunderstandings with the IDisposable pattern and explain exactly where it needs to be implemented and how you can best make use of it.

What is IDisposable for?

In the most primitive sense, IDisposable is only an interface with a single method (named Dispose()) that does absolutely nothing. It came to life because there is no deterministic finalization in .NET, which simply states that .NET doesn't give any guarantee on when an object's finalizer will be called. Any object the programmer lets go of will stay in memory as it was until the garbage collector comes around to collect and kill it.

Without this pattern, whenever you needed to ensure that a resource (like an open file handle or, for our fellow game programmers, a graphics device resource) is freed again, you'd have to implement some method like CloseFile() or ReleaseTexture() that could be used to let go of these resources immediately. IDisposable is merely a standardized fashion to provide such a method to the user.

Their are basically two ways (or levels) you can choose when implementing the IDisposable interface. Let's take a look at both now:

The Simple Implementation

You can implement IDisposable in a very simple manner by only adding IDisposable to the list of interfaces implemented by your class and writing the finalization code into the Dispose(). Here's an example of that:

class MyClass : IDisposable {

  /// <summary>Immediately releases all resources owned by the object.</summary>
  public void Dispose() {
    // Perform object finalization here
  }

}

This implementation is straight-ahead and very lightweight. Unless you've got actual unmanaged resources to kill off, this should be your preferred implementation.

The Full Implementation

Here is the IDisposable implementation you will see demonstrated in the MSDN docs and that a lot of developers have come to accept as the only way to add finalization code to a class. It is only required for a specific case of finalization, namely that of releasing directly used unmanaged resources.

class MyClass : IDisposable {

  /// <summary>Finalizer that will be called by the GC.</summary>
  ~MyClass() {
    Dispose(false); // false == not being called by user code
  }

  /// <summary>Immediately releases all resources owned by the object.</summary>
  public void Dispose() {
    Dispose(true); // true == called by user code

    // We are finalized already, tell the GC that it doesn't
    // need to call the finalizer anymore
    GC.SuppressFinalize(this);
  }

  /// <summary>Immediately releases all resources owned by the object.</summary>
  /// <param name="calledByUser">
  ///   If true, the object is being disposed explicitely and can still access
  ///   the other managed objects it is referencing.
  /// </param>
  protected virtual void Dispose(bool calledByUser) {
    if(calledByUser) {
      // Perform finalization of managed objects here
    }
   
    // Perform finalization of unmanaged objects here
  }

}

There are several things to note here. For once, there is an actual finalizer involved in this implementation of the Dispose Pattern. In .NET, the finalizer of an object is only ever invoked by the garbage collector. When that happens, you don't know whether any other objects you're referencing have already been destroyed. Thus, you mustn't access any managed objects when the finalizer is invoked. As you can see, it calls the Dispose() method with its parameter set to false.

The other important part is the IDisposable implementation. It calls the Dispose() method with its parameter set to true so it knows that other objects referenced by the instance can be accessed without trouble. And then there's a line containing the statement GC.SuppressFinalize(this);.

When you write a class that defines a finalizer, the GC will have to track all instances you create of that class in its finalization queue. This makes it a little bit harder for the GC to reclaim the memory because it has to wait until the object's finalizer has run. With GC.SuppressFinalize(), you tell the GC that you have just finalized the object yourself and that it doesn't have to bother with calling the finalizer anymore.

As stated before, this pattern is only really of any use when your class is working with unmanaged resources. And even then, the finalizer merely acts as a safety net to guarantee that the unmanaged resources are released if you forget to call Dispose() in your code.

Why Dispose Managed Objects?

The garbage collector will reclaim the memory used by your objects anyway, so why should you ever need to implement a Dispose() method for purely managed objects?

Reason 1

UML diagram of the DatabaseConnection example explained below

The most obvious case is when your object references other objects that implement the IDisposable interface. Assume, for a moment, that there was a DatabaseConnection class that required you to call its Dispose() method to close the connection to some database again. If you wrote your own DatabaseConnectionWrapper, you'd have to implement IDisposable there, too (but only the lightweight flavor).

Reason 2

UML diagram of the LevelManager example explained below

Your managed object registers itself to a long-living object in some way. If, for example, you were going to write a computer game and had some LevelManager class that provided an event named LoadingProgressUpdated, there is a subtle problem. While a level is loading, the user is presented with a nice loading screen, which, in terms of code, is a class that renders a progress bar and subscribes to the LoadingProgressUpdated event. Unless you explicitely dispose the loading screen, it stays in memory until the LevelManager is destroyed. Worse even, with each level you load, another instance might be added to that event's subscription list -- almost a classical memory leak.

So whenever you need to clean up something, like unsubscribing from the events of another class, reset some state or take down an unmanaged resource you use, the lightweight IDisposable implementation is your friend.

IDisposable and the using Statement

If you're writing exception-safe code, you're probably used to having try .. finally blocks in several places of your code:

void Test() {
  MemoryStream memory = new MemoryStream(104857600); // 100 MB
  try {
    // Do something here
  }
  finally {
    memory.Dispose();
  }
}

The MemoryStream class implements IDisposable and you're allocating a considerable amount of memory, so you dutifully want to free that memory again as soon as possible, even when an exception is thrown.

C# has a little gimmick that allows you to shorten such cases where you want to call Dispose() on a short-lived objects even when an exception occurs. The above code, rewritten to use the using directive, looks like this:

void Test() {
  using(
    MemoryStream memory = new MemoryStream(104857600); // 100 MB
  ) {
    // Do something here
  }
}

Not only is this code shorter, it also guarantees that the variable for the short-lived object (memory in this case) goes out of scope after it has been disposed, completely eliminating the risk of accidentally using the disposed MemoryStream in the code that follows.

Piotr Kolodziej's picture

"Unless you explicitely

"Unless you explicitely dispose the loading screen, it stays in memory until the LevelManager is destroyed"

Why is that? Why it stays in memory until level manager is destroyed? I cannot understand how these classes are related to each other... Is thi because LevelManaager created object of class LoadingScreen?

Cygon's picture

It's Because of the Event

When you subscribe to the event of another class, like to the LevelManager in my example, this means that the LevelManager obtains a reference to your subscriber.

Thus, the subscriber will stay in memory until either the LevelManager is disposed (or until your class unsubscribes from the LevelManager).

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Lines and paragraphs break automatically.
  • Allowed HTML tags: <br> <a> <em> <strong> <u> <i> <b> <cite> <blockcode> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <pre> <span>
  • You can highlight code with any of the following tags: <blockcode>

More information about formatting options