Deterministic Finalization I - a primer for CLR Dispose

A large subject like DF needs a few posts. My generalized plan to lay it out will start by describing the CLR's Dispose pattern, how our DF pattern works, and finally how the two patterns fit together.

The CLR's Dispose patterns can be quite confusing. It took me a while to get my mind around Dispose() and Dispose(bool), and I'm still not sure I fully understand it. But, I'm going to take a crack at it. Most of what I know about Dispose/Finalize patterns comes from two sources; talks with Brandon, and this excellent whitepaper on the subject. It's wordy, but it will do a far better job explaining Dispose than I can, and it will talk about more Dispose patterns than I plan to. In fact, it might be a good idea to read that whitepaper, and then return here, to see how my take on it differs from your own. I'll take this opportunity to remind you that this is my opinion of the way things are, not the way they really are.

The usefulness of Disposing is primarily for freeing unmanaged resources. In a nutshell, the Garbage Collector really only gets invoked when there's memory pressure. But the GC can't "feel" pressure from unmanaged resources. If you have a bunch of objects hanging around that have gobs of natively allocated memory (C++ new, or Marshal.AllocHGlobal), the GC won't know about this memory pressure, and won't begin freeing up these objects. (It may notice the free memory being reduced, but it has no sense of which objects are holding the memory.) The aforementioned whitepaper also talks about database handles, file handles, and message queue handles as other forms of unmanaged resource.

When creating .NET Disposable types, you usually want to create a Finalize (C# destructor syntax), Dispose(), Dispose(bool), and inherit from System.IDisposable. Almost all of your actual object cleanup will occur inside Dispose(bool). That's the pattern they came up with, and it's a good way to keep all your cleanup in one place. We'll get to cases in a second, but first, here's a hunk of C# code plaigarized from the whitepaper:

public class BothType: IDisposable{
public void Dispose(){

~BothType(){ //finalizer

protected virtual void Dispose(bool disposing){
//'Disposable' managed resource cleanup code
//Unmanaged resource cleanup code

When Disposing, the first call is always to Dispose(). Biggest take-away from one of my talks with Brandon. All disposing starts with that call. The CLR way of doing "chaining" destructors is the manual Dispose(bool) pattern. Basically, the user calls Dispose(), and inside that function, Dispose(true) is called. This does a callvirt on Dispose(bool), which will thunk down to the lowest child instance. Then, you have the manual base.Dispose(disposing) calls that will walk up the chain. I had to step up to my whiteboard for a while to prove to myself that this pattern works - but it does.

The Finalizer is a backup system in this paradigm, only there to ensure that your unmanaged resources get cleaned up. That is, if a Disposable object is being Finalized, it's probably a mistake - the user forgot to Dispose(). So a call to Finalize() yields a call to Dispose(false), which takes care of only those unmanaged resources it holds, those that the GC is unable to clean up.

Note: a call to Dispose(false) should only clean up the unmanaged resources. If we're finalizing, we have no discrete order for finalization. If I try and Dispose objects the GC is aware of within a finalizer, I could be in for a world of pain, because those objects might already be collected. Finalization is not deterministic. There was an MSDN TV episode where Brad Abrams tells an antecdote about a bug where they were calling Console.WriteLine() in their Finalizer and getting a NullReferenceException, because the Console object had already been finalized. Talk about hard to track down...

You clean up your managed resources inside the if(disposing) scope. Note that in that scope, you should only clean up your Disposable member objects. Don't bother with objects that don't have a Disposer, those objects will be cleaned up just fine by the GC. It's a trade-off you make between those objects you need to get rid of, to free up scarce resources, and not having to clean it all up right away. Like at home; I might do the dishes on a weeknight (especially if I've run out of ramen bowls), but I don't go vaccuuming the floor every time I walk on it.

The call to GC.SuppressFinalize() shouldn't be overlooked - it ensures the Garbage Collector won't Finalize an object that's already been Disposed. In general, you're either going to Dispose an object, or the GC will Finalize it, not both.

Though this is the most flexible cleanup model, it isn't the only cleanup model for objects in the CLR. The whitepaper goes over most of them. The simplest case is where your object has no Disposable members, or unmanaged references. ("Simple" objects.) No Disposers or Finalizers needed there, the CLR will clean everything up for you. Or, your object might only have non-scarce resources and the aforementioned Simple objects, in which case you might have a Finalizer, but not a Disposer. But, if you've got only unmanaged resources, or both unmanaged and managed resources, you probably want both a Disposer and a Finalizer, where the Finalizer is more a safety net than intended use. The whitepaper also talks about the case where you might want only a Disposer. While this case does exist, I think it should be used with caution. If you or your user forgets to Dispose() such an object, your unmanged resources could be hanging around for a very long time.

Coming in Part II, I'll discuss the C++ DF model, and how it fits with the CLR's Dispose() pattern. (Depending on how long it takes, that discussion might be split into two parts.) I'm also planning on a Part III (or IV), where I'll talk about the C# using declaration (see 2.4 of the whitepaper), and how C++ tackles the same problems.