Alright! Here it is: the revised “Dispose, Finalization, and Resource Management” Design Guideline entry. I mentioned this work previously here and here. At ~25 printed pages, it's not what I would consider to be a minor update. Took me much longer than anticipated, but I'm happy with the result. I got to work with and received good amounts of feedback from HSutter, BrianGru, CBrumme, Jeff Richter, and a couple other folks on it... Good fun.
As usual, questions, comments, and feedback are requested. Hope it comes across formatted half-decently.
Update: 4/16/05: Fixed a few typos that were bugging me & came up during the internal review of this doc.
The CLR’s garbage collector (GC) does an amazing job at managing memory allocated directly to CLR objects, but was explicitly not designed to deal with unmanaged memory and OS-managed resources. In many cases, objects running inside the CLR need to interact with resources from the unmanaged world. There’s a considerable gap between these two worlds, requiring a bridge to explicitly manage the touch points. The responsibility for building such a bridge lies mostly in the hands of managed API developers.
The primary goal when managing such resources is quite simply to make use of them in the most efficient manner. This is especially important when resources are limited, e.g. when only a limited quantity is available. You should strive to provide your users with the controls needed to acquire and release resources as needed, in addition to ensuring a safety net exists to prevent long-running resource leaks. Thankfully, the .NET Framework comes with an array of abstractions to hide the details of unmanaged resources from most platform developers (e.g. with HWnds, database connections, GDI handles, SafeHandle), but the sheer number of resource types available means that you’ll sometimes need to write code to manage them yourself.
This section documents the recommended pattern for implementing both explicit and implicit resource cleanup. This is often referred to as the “Dispose” or “IDisposable” pattern, and normally involves the IDisposable interface, a Dispose method for explicit cleanup, and in some cases a Finalize method for implicit cleanup. Implementing this pattern correctly—when appropriate—is critical to ensuring proper, timely cleanup of resources, and also to provide users with a deterministic, familiar way of disposing of resources.
Annotation (Krzysztof Cwalina): Many people who hear about the Dispose pattern for the first time complain that the GC isn’t doing its job. They think it should collect resources, and that this is just like having to manage resources as you did in the unmanaged world. The truth is that the GC was never meant to manage resources. It was designed to manage memory and it is excellent in doing just that.
Managed types must occasionally encapsulate control over resources that are not managed by the CLR. In such cases, the smallest possible class, or “wrapper,” should be used to encapsulate these resources. Ideally, this thin wrapper should contain just allocation along with provisions for basic access to and freeing of the resources. Enclosing classes can be used to provide a more natural view over the resource through abstracted APIs, taking care not to expose the internal resource wrapper. Following this pattern helps to mitigate many of the risks and difficulties outlined further in this section.
An explicit Dispose method should always be provided to enable users to free resources owned by an instance deterministically. Implicit cleanup by means of a Finalize method is also required when a class directly owns such a resource, but often the resource wrapping class—such as SafeHandle for example—will take care of this for you. This leaves only the task of creating an explicit cleanup mechanism to the Framework developer. We discuss both implicit and explicit cleanup in the next section.
Implicit Cleanup
Implicit cleanup should always be provided by protecting resources with a SafeHandle. In fact, implementing finalizers by hand is seldom necessary thanks to the introduction of this type in the .NET Framework 2.0. If supplementary finalization semantics are required, you can implement the protected Finalize method yourself using the special finalizer syntax in your favorite language (e.g. ~T() in C# and !T() in C++). The runtime will invoke Finalize for you nondeterministically as part of the GC’s finalization process, providing a last chance for your object to ensure resources are released at the end of its lifetime. By nondeterministic, this simply means that the GC will call Finalize at an undefined point in time after there are no longer any live references to your object. Correctly implementing finalizers by hand is a notoriously difficult task—please see details further below should you decide you have to do so.
Explicit Cleanup
In every case where a type owns resources—or owns types which themselves own resources—you should give users the ability to explicitly release these them. Developers will then have the option to initiate the release of resources once the object is no longer in use. This alleviates some of the reliance on the GC for destruction of resources (something which can subtly harm performance), and also provides users a deterministic way to reclaim resources. Moreover, if the external resource is scarce or expensive, as is the case with OS-allocated handles (e.g. file handles), performance can be improved and resource starvation avoided if they are released once no longer needed. Explicit control should always be provided with a Dispose method based on the IDisposable interface (in C++, simply write a destructor, ~T(), for your type T; the compiler will generate the entire underlying Dispose mechanics).
Read below for more information on the general pattern, as it actually involves more than simply writing a Dispose method when creating a non-sealed class.
Annotation (Clemens Szyperski): The only problem is that automatic management of memory and objects makes it difficult to ensure that resources held by objects are released deterministically (that is, early). The reliance on the GC can lead programmers to think that they don't need to worry about this anymore, which is not the case. In fact, any object that implements IDisposable should be mentally tagged with a red flag and should not be allowed to fall off the scene without Dispose having been called. The finalization/safe handle safety net is really not good enough to prevent lousy user experiences - such as a file remaining locked for an unexpectedly long time after "save" and "close" of a document window (but with the app still running). Careful use of AppDomains and their forced unloading (which triggers safe handles) is sometimes a way to deal with this rigorously.
Annotation (Herb Sutter): Finalizers are actually even worse than that. Besides that they run late (which is indeed a serious problem for many kinds of resources), they are also less powerful because they can only perform a subset of the operations allowed in a destructor (e.g., a finalizer cannot reliably use other objects, whereas a destructor can), and even when writing in that subset finalizers are extremely difficult to write correctly. And collecting finalizable objects is expensive: Each finalizable object, and the potentially huge graph of objects reachable from it, is promoted to the next GC generation, which makes it more expense to collect by some large multiple.
Today, on most GC systems including .NET, the right advice is: When you have to do cleanup for your object, you almost always want to provide it in a destructor (Dispose), not in a finalizer. When you do want a finalizer, you want it in addition to a destructor (Dispose), not instead of Dispose.
Annotation (Brian Grunkemeyer): There are two different concepts that are somewhat intertwined around object tear-down. The first is the end of the lifetime of a resource (such as a Win32 file handle), and the second is the end of the lifetime of the object holding the resource (such as an instance of FileStream). Unmanaged C++ provided destructors which ran deterministically when an object left scope, or when the programmer called delete on a pointer to an object. This would end the resource’s lifetime, and at least in the case of delete, end the lifetime of the object holding onto the resource. The CLR’s finalization support only allows you to run code at the end of the lifetime of the object holding a resource. Relying on finalization as the sole mechanism for cleaning up resources extends the resource’s lifetime to be equal to the lifetime of the object holding the resource, which can lead to problems if you need exclusive access to that resource or there are a finite number of them, and can hurt performance. Hence, witness the Dispose pattern for managed code, allowing you to define a method to explicitly mimic the determinism & eagerness of destructors in C++. This relegates finalization to a backstop against users of a type who do not call Dispose, which is a good thing considering the additional restrictions on finalizers.
Now that you understand this, note that the Finalize & Dispose methods serve very different purposes. Unfortunately, they’re surfaced differently in different languages. C# calls a finalizer a destructor, forcing you to use ~T(), whereas Dispose is a normal method. In C++ starting with version 2 of the .NET Framework, Dispose methods are generated from code written using the destructor syntax (~T), whereas the finalizer will be specified using some other syntax like !T(). C# arguably emphasized the wrong aspect of object lifetime (instead of resource lifetime) by giving the special C++ name the wrong meaning. But note that when you see the word destructor or ~T() in any discussion on Dispose() or object lifetime, pay attention to exactly what the author intended. I prefer using “finalizer” and “dispose” to alleviate any language-induced confusion. Note that this pattern also calls for Dispose(void) & Dispose(bool) in places.
Generally speaking, it is considered good design for consumers of a disposable instance to call Dispose when they are done using it. This is simpler in languages like C# which provides a “using” block to automate calling Dispose for local objects, and in languages like C++ which fully automate “using” with stack-based semantics. However, implicit cleanup is still necessary for cases where a user neglects or fails to invoke the explicit release mechanism, essentially transferring responsibility to the runtime to perform the cleanup.
If your class is not sealed and has to perform resource cleanup, you should follow the pattern exactly as it appears below. For sealed classes, this pattern need not be followed, meaning you should simply implement your Finalizer and Dispose with the simple methods (i.e. ~T() (Finalize) and Dispose() in C#). When choosing the latter route, your code should still adhere to the guidelines below regarding implementation of finalization and dispose logic.
This pattern has been designed to ensure reliable, predictable cleanup, to prevent temporary resource leaks (as a result of skipped disposes, for example), and most importantly to provide a standard, unambiguous pattern for compilers and developers to author disposable classes and for programmers consuming disposable instances. The description below also offers guidance on when and why you should implement a finalizer, as not every disposable type needs one. Lastly, versioning classes that require resource cleanup poses some challenges, especially when introducing a new type into an existing class hierarchy. This is also discussed below.
Annotation (Herb Sutter): You really don’t want to write a finalizer if you can help it. Besides problems already noted earlier in this chapter, writing a finalizer on a type makes that type more expensive to use even if the finalizer is never called. For example, allocating a finalizable object is more expensive because it must also be put on a list of finalizable objects. This cost can’t be avoided, even if the object immediately suppresses finalization during its construction (as when creating a managed object semantically on the stack in C++).
If using C++, simply write the usual destructor (~T()) and the compiler will automatically generate all of the machinery described later in this section. In the rare cases where you do want to write a finalizer (!T()) as well, the recommended way to share code is to put as much of the work into the finalizer as the finalizer is able to handle (e.g., the finalizer cannot reliably touch other objects, so don’t put code in there that needs to use other objects), put the rest in the destructor, and have your destructor call your finalizer explicitly.
þ Do implement the dispose pattern when your type is unsealed and contains resources that explicitly need to be or can be freed, for example raw handles, or other unmanaged resources. This pattern provides a standardized means for developers to deterministically destroy or free resources owned by an object. It also aids subclasses to correctly release base class resources.
þ Do fully implement the dispose pattern on classes with disposable subtypes, even if the base type does not own resources. This will enable programmers coding against the base class to dispose of further derived instances properly. A great example of this pattern is the v2.0 System.IO.Stream class. Although it is an abstract base class which doesn’t hold on to resources, most of its subclasses do; because of this, it follows this pattern.
þ Do implement IDisposable only on classes where a class in its parent hierarchy has not already done so. The public void Dispose() method should be left final (i.e. not marked virtual) and consist of only two operations: a virtual call to Dispose(true) and a call to GC.SuppressFinalize(this), in that order. The call to SuppressFinalize should only occur if Dispose(true) executes successfully—thus, you should not place the call inside a finally block. Types inheriting from other classes which already follow this pattern can and should reuse the existing implementation of Dispose().
Annotation (Brad Abrams): We had a fair amount of debate about the relative ordering of calls in the Dispose() method. E.g.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
Or
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
We opted for the first ordering as it ensures that GC.SuppressFinalize() only gets called if the Dispose operation completes successfully.
Annotation (Jeffrey Richter): I too wrestled back and forth with the order of these calls. Originally, I felt that SuppressFinalize should be called prior to Dispose. My thinking was this: if Dispose throws an exception then, it will throw the same exception when Finalize is called and there is no benefit this and the 2nd exception should be prevented. However, I have since changed my mind and I now agree with this guideline that SuppressFinalize should be called after Finalize. The reason is because Dispose() calls Dispose(true) which may throw but when Finalize is called later Dispose(false) is called and this may be a different code path than before and it would be good if this different code path executed. And, the different code path may not throw the exception.
Annotation (Brian Grunkemeyer): The ordering is important to give your finalization code (in Dispose(false)) a chance to clean up the resource even if some higher level guarantees usually made when disposing the object can’t be made. A Dispose method should be able to guarantee correctness & free the resource when it completes. But if the code for guaranteeing correctness fails, we fall back on the finalizer, which calls Dispose(false). This may entail some amount of corruption or data loss, but at that point the corruption is inevitable, and at least we can ensure we don’t have a resource leak. Finalization code already has to deal with partially constructed objects, so the additional burden of dealing with an object where the Dispose(true) code path failed shouldn’t be significant.
þ Do create or override the protected virtual void Dispose(bool disposing) method to encapsulate all of your cleanup logic. All cleanup should occur in this method, predicated—if necessary—by the disposing argument. The argument’s value will equal false if being invoked from inside a finalizer, which should be used to ensure any code running from a finalizer is careful to follow the Finalize guidelines detailed in the next section.
Annotation (Jeffrey Richter): The idea here is that Dispose(Boolean) knows whether it is being called to do explicit cleanup (the Boolean is true) versus being called due to a garbage collection (the Boolean is false). This distinction is useful because, when being disposed explicitly, the Dispose(Boolean) method can safely execute code using reference type fields that refer to other objects knowing for sure that these other objects have not been finalized or disposed of yet. When the Boolean is false, the Dispose(Boolean) method should not execute code that refer to reference type fields because those objects may have already been finalized.
Annotation (Joe Duffy): Jeff’s comment might seem to be an overstatement under careful examination. For example, can’t you safely access reference type objects that aren’t finalizable? The answer is yes you can, iff you are certain that it doesn’t rely on finalizable state itself. This reliance could be directly or indirectly through complex relationships with other reference types--a pretty nontrivial thing to figure out (and something which is subject to change from release to release). So unless you’re 100% certain, just avoid doing it.
þ Do make a call to your base class’s Dispose(bool disposing) method (if available) as the last operation in your Dispose(bool) implementation. Make sure to preserve the value of the disposing argument by passing the same value that your method received. This makes sure that base classes are given a chance to clean up of resources, but not before your cleanup code executes (which could rely on their presence).
þ Do implement a finalizer if your object is responsible for controlling the lifetime of at least one resource which does not have its own finalizer. Types like SafeHandle, for example, have their own finalizers responsible for cleaning up resources. In other cases, however, users will often neglect to write code which guarantees executing explicit dispose logic. If your base class has already overridden Finalize to follow this pattern, you should not override it yourself, as it will make the call to your virtual Dispose(bool) override appropriately.
When implementing your finalizer, place all finalization cleanup logic inside the Dispose(bool disposing) method. Your Finalize method should make a single virtual call to Dispose(false) and nothing more. As noted above, any logic not appropriate to execute during finalization should be written so as not to fire if the disposing argument is false. Such restrictions are discussed further below.
ý Do not re-implement the IDisposable interface, override void Dispose(), or override Finalize if a base type in your class hierarchy has already defined them according to this pattern. You should just override Dispose(bool) and add your cleanup logic, making sure to call upwards to your base class. Re-implementing Finalize can actually result in unnecessary calls to Dispose(bool).
Annotation (Joe Duffy): Having multiple finalizers in a class hierarchy which follows this pattern can result in redundant calls to perform cleanup logic. A virtual finalize method that automatically chains to base.Finalize()—precisely what the C# compiler creates by default—will make n virtual calls Dispose(bool), where n is the number of finalizers the hierarchy following this pattern. This happens because Finalize is called virtually which in turn virtually calls Dispose(bool), both of which chain to their base classes. So long as types are written to be resilient to multiple disposes (ignoring unnecessary calls), the only problem this will create is the subtle performance overhead to make the redundant chains of virtual method calls.
Annotation (Herb Sutter): Note that what Joe mentions about C# doesn’t happen in C++, because C++ generates the recommended machinery whereby Dispose(bool) in the only point that chains to the base (disposer or finalizer).
ý Do not create any other variations of the Dispose method other than the two specified here: void Dispose() and void Dispose(bool disposing). Dispose should be considered a reserved word in order to help codify this pattern and prevent confusion among implementers, users, and compilers. Some languages (like C++) may choose to automatically implement this pattern on certain types, such as taking methods like ~T() and !T(), and folding them into one Dispose(bool) method.
Simple Example w/out Finalize (C#)
For the majority of types implementing the Dispose pattern, you will not need to implement your own Finalize method. This example shows the simple case, for example when using a SafeHandle to take care of the implicit cleanup:
public class SimpleCleanup : IDisposable
{
// some fields that require cleanup
private SafeHandle handle;
private bool disposed = false; // to detect redundant calls
public SimpleCleanup()
{
this.handle = /*…*/;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (handle != null)
handle.Dispose();
}
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
Complex Example w/ Finalize (C#)
Consider this example of the shell of a correct base-type implementation of the more complex pattern. That is, an implementation that has its own Finalizer. This also demonstrates what a class newly introducing cleanup into a class hierarchy should look like:
public class ComplexCleanupBase : IDisposable
{
// some fields that require cleanup
private bool disposed = false; // to detect redundant calls
public ComplexCleanupBase()
{
// allocate resources
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// dispose-only, i.e. non-finalizable logic
}
// shared cleanup logic
disposed = true;
}
}
~ComplexCleanupBase()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
This code snippet shows what a class extending ComplexCleanupBase would do to hook into the Dispose and Finalize lifecycle to ensure correct cleanup behavior:
public class ComplexCleanupExtender : ComplexCleanupBase
{
// some new fields that require cleanup
private bool disposed = false; // to detect redundant calls
public ComplexCleanupExtender() : base()
{
// allocate more resources (in addition to base’s)
}
protected override void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// dispose-only, i.e. non-finalizable logic
}
// new shared cleanup logic
disposed = true;
}
base.Dispose(disposing);
}
}
Notice that it does not re-implement Dispose or Finalize as its parent class already does so. The base type implementations will correctly forward to the most derived Dispose(bool) method so that resource cleanup occurs in the correct order.
Example w/ Finalize (C++)
Implementing this in C++ can be accomplished using the new syntax:
public ref class ComplexCleanupBase
{
private:
bool disposed;
public:
ComplexCleanupBase() : disposed(false)
{
// allocate resources
}
// implicitly implements IDisposable
virtual ~ComplexCleanupBase()
{
Console::WriteLine("Base::~dtor");
if (!disposed)
{
// dispose-only, i.e. logic not suitable for finalizer
this->!ComplexCleanupBase();
disposed = true;
}
}