The “Dispose pattern” describes the proper way for a type to implement Dispose and/or Finalize. It applies in different ways to different .NET languages. In C++/CLI, for example, the Dispose pattern is automatically implemented by managed types with destructors and/or finalizers. This post is primarily aimed at C# developers.
The Dispose method of the IDisposable interface is used to free the resources used by an object, which may include unmanaged resources that need to be released, managed resources that need to be disposed, and other miscellaneous cleanup.
The Finalize method of Object (that is, the “finalizer”) is similar, but is called automatically after the object is garbage collected, and should only be overridden to release unmanaged resources, since managed resources may already have been finalized and shouldn’t be accessed. (In C#, you use the “destructor syntax” to create a finalizer.)
First things first: you don’t need a finalizer. Finalizers can only be used to release unmanaged resources. Your clients should be calling Dispose, which will release those resources. If you need to release those resources even if the client forgets to call Dispose, you should wrap each resource in a separate finalizable class derived from SafeHandle or CriticalHandle or CriticalFinalizerObject. Feel free to implement a simple finalizer for debugging purposes, but consider removing it from shipping code.
If you’re really, really sure that you need to ship with a finalizer, be sure to understand and obey all of the rules. Otherwise, with finalizers out of the way, we can discuss a simpler set of rules for implementing the Dispose pattern.
The simplest case is a sealed class whose base class doesn’t already implement IDisposable. Just implement IDisposable and do your cleanup in the Dispose method.
If your class isn’t sealed (and its base class doesn’t already implement IDisposable), you need to provide the proper mechanism for derived classes to add their own cleanup. Specifically, you need to add a virtual method, also called Dispose, that takes a Boolean argument that indicates whether it is being called from Dispose or from the finalizer. (If a derived class needed a finalizer, it would add one that called Dispose(false). The call to GC.SuppressFinalize would prevent it from being called if the object was already disposed.)
(If your class is abstract, you may be tempted to make Dispose(bool) abstract as well. Unfortunately, that breaks the pattern enough to cause derived classes implemented in C++/CLI to enter an infinite loop when they are disposed.)
If your base class already implements IDisposable, it ideally conforms to the Dispose pattern, which means it has a virtual Dispose(bool) that should be overridden to do cleanup.
If your base class implements IDisposable but doesn’t conform to the Dispose pattern, you should introduce the Dispose pattern for your derived classes.
(If the Dispose method of the base class isn’t virtual, you won’t need the “sealed override”, but you’ll need to reimplement IDisposable. If the Dispose method of the base class is abstract, you won’t be able to call “base.Dispose()”.)
Finally, if your base class doesn’t conform to the Dispose pattern, but your class is sealed, you can keep it simple.
(If the Dispose method of the base class isn’t virtual, you won’t be able to “override”, so you’ll have to reimplement IDisposable.)
I’d still like to talk about ensuring that Dispose can be called multiple times, when to throw ObjectDisposedException, the thread safety of this pattern, and disposable value types (structs), but those topics will have to wait for future posts.
Posted by Ed Ball on February 01, 2008