2008-06-01

Dispose Design Pattern

Given all the benefits of the Garbage Collector (GC) in .NET, the IDisposable/Dispose pattern is a necessary evil. It does make the code more complicated, harder to manage, and more error-prone. However, if you want to rely solely on finalizers to release resources, your application isn't going to fly.

Releasing resources (database connections, handles to open files, blocks of memory, etc.) as soon as they are no longer needed is prudent. Also, dropping the references to other objects is a great idea, because it helps the GC reclaim unused memory more efficiently. For the GC, the more null references, the better.

To reiterate, the Dispose() method should be used to:

  • Release managed resources (in the realm of CLR);
  • Release unmanaged resources (in the native realm);
  • Drop references (set them to null).
There is one fortunate side-effect of the IDisposable pattern: it may be used for canceling pending asynchronous operations. Imagine a database query form, which can be closed by a user any moment. The best way to handle this situation is to implement the Dispose method on a class that performs the asynchronous query, which would close database connections, effectively, canceling the database request. One thing to note here is that in this scenario, concurrency comes into play, since Dispose may be invoked simultaneously with the async callbacks.

Thus, Dispose() may also be used to:
  • Cancel any pending async operations.
What are finalizers good for then? Since finalizers are not guaranteed to execute at all, and they impose a considerable overhead onto the GC - the answer is: pretty much nothing. According to MSDN, finalizers should be used to release unmanaged resources. There is another school of thought, which suggests to not use finalizers at all. In the context of our discussion, however, there is one beneficial use of finalizers:
  • To warn if the Dispose() method hasn't been called (via asserting or logging).
It should be self-evident from the failed assertion, who was responsible for disposing of the object properly. To make it even more apparent, one could capture the callstack in the constructor, which would identify the object's creator. The GC overhead can be avoided in the correct use-case by hinting the GC to skip the finalizer with the SuppressFinalize method.

There is an IDisposable pattern (C# .NET) posted on MSDN. I personally prefer the following variation:



using System;
using System.Diagnostics;

namespace MyProject
{
class MyClass:
IDisposable
{
~MyClass()
{
DisposeOfUnmanagedResources();

// should have called Dispose!
Debug.Assert(false);
}

public void Dispose()
{
DisposeOfUnmanagedResources();

// release managed resources

GC.SuppressFinalize(this);
}

private void DisposeOfUnmanagedResources()
{
// release unmanaged resources
}
}
}


(The sample above was formatted using http://www.manoli.net/csharpformat/)

Compared to the original pattern on MSDN, here:
  • DisposeOfManagedResources()/DisposeOfUnmanagedResources() replace the ambiguous Dispose(bool) method;
  • The finalizer ~MyClass() would raise an assertion (in Debug builds) if the user of MyClass hasn't called Dispose().
That's it.

No comments: