The long-awaited return of DF

Back from the dead

Well, not precisely dead, but I certainly began feeling that way - shipping a product is hard work, and it is incredibly easy to get "heads down." Here on the VCQA team, we're very focused on stabilization. A lot of testruns. Harsh bug bars. Only the highest quality fixes. Beta 2 is out the door, and it's time to tighten up for the endgame.  As we're fond of saying: the product has 2005 on it.  By my count, we've got about 6 months to ship a product.

About that DF thing. While composing what was to be my final post on this whole DF topic, I ran into a brick wall. I couldn't seem to explain how to tie it together. It didn't fit well enough with the Dispose(bool) pattern. There were too many caveats - too many special cases that the user had to handle, both from the authoring end and the consuming end.

The new, new DF is more straightforward. When I heard about it, I was glad. Glad as a user - as a tester, when I hear "redesign," it sounds an awful lot like "here's a bunch of extra testing work, and some of those cool tests you wrote can go in the wastebasket." Even though the redesign impacted my already heavy workload (my dentist: "Do you know you're grinding your teeth at night?"), I was happy to see we were doing what I felt was right.

The executive overview is this : we implement the Dispose(bool) pattern for you, and hook it up in the best way possible to your base. (If you have one.)

Here, I'm only going to delve into the first part; that "best way possible" stuff requires a complex decision tree that I don't have the energy to iterate over right now.

We still have the same general design: ~T is basically your destructor, which we implement with Dispose(void), and !T is basically your finalizer, implemented through Finalize(void). What we've added to the mix is Dispose(bool).

Here's how I would write what the compiler does for you:

Dispose(bool disposing) {
if(disposing) {
//call your ~T code
  } else { /* call your !T code */ }

  //contingent upon what your base class looks like we may call

The major difference between our Dispose(bool) and the one suggested by the CLR Dispose pattern is the else. This is mostly because the user writes Dispose(bool) in the CLR version - in the C++ version, the compiler authors it, so it is left up to the user how to handle it. (The best thing to do, usually, is to invoke the finalizer from the destructor, unless you *really* know what you're doing.)

Typically, you release Disposable managed items from your destructor and unmanaged items for your finalizer. I was confused by this at first.  For some reason, I thought it would be the reverse. But eventually, I understood why.

An object allocated on the GC heap has a few potential invocation states for cleanup: user-invoked, intentional finalization, and unintentional finalization. Intentional finalization is easiest, so we'll tackle it first.  Sometimes, the cleanup of an object isn't a priority. If you're just dealing with managed heap space, for example, the GC is great at that. It prioritizes, finds the right times to go about cleaning up, and is quite efficient at it.

As to user-invoked cleanup; when dealing with a scarce or untracked resource (e.g. hunks of unmanaged heap space, or database connections), you typically want to free up those resources as soon as you know you're done with them. This is why you want objects with Disposers: it's good engineering practice to let go of scarce resources ASAP.  That's why you want to provide them to your users.  However, not all of the people using your type may get this (or care) and - this is important - there's no way in the CLR to get them to care. You can't force it. Heck, you yourself are bound to forget now and then.

So we need to handle unintentional finalization, to make sure we don't leak. In your Finalizer, you clean up those resources that wouldn't be cleaned up on their own. And those are the only resources you should clean up in a Finalizer.  You should read that again, it's an important distinction in my mind.

Finalization is NOT deterministic. When your finalizer is invoked by the CLR, you can't be certain that any finalizable objects you have references to have not already been finalized. Trying to Dispose something that's already been finalized is dangerous.

With all these strong admonitions and specifics, a good example is due - look for that in an upcoming post.

Comments (9)
  1. justinsb says:

    "Typically, you release Disposable managed items from your destructor and unmanaged items for your finalizer." I don’t think this is an accurate summary. In your destructor (called from Dispose), you should release managed and unmanaged resources. In your finalizer you should again try to release unmanaged resources – if they haven’t already been cleaned up by an explicit Dispose call. In practice, the finalizer won’t get called if Dispose has been called, so checks in the finalizer as to whether an unmanaged resource has been disposed are possibly redundant.

    Putting clean-up into the finalizer ensures that unmanaged resources will be released, but also allows callers to release unmanaged resources deterministically. If you release only managed resources in your destructor, the finalizer is not called whenever Dispose is called, and your unmanaged resources will leak.

    It seems to me that this this abstraction is actually unhelpful. What you want is two "release" functions, one which releases only managed resources (automatically called only from Dispose), and one which only releases unmanaged resources (automatically called from Dispose and Finalize). As it stands, we just have to write more boilerplate code, but it’s now different boilerplate from that we write in C#. What would be useful is if both the finalizer and the destructor were called from Dispose, but that doesn’t appear to be the case.

  2. Anonymous says:

    Interesting Finds

  3. arich says:

    I didn’t make this compeltely clear, but I have to echo your sentiments – were I to have designed this, I would have made it work exactly like the C# version.

    As it is, what I typically do (and I believe we recommend) is explicitly call the finalizer from the destructor code. I think the logic is that we leave that decision up to the user – it’s a little harder, but it provides more flexibility. (Just like C++ always is. 🙂 )

    I’ve got about 3/4 of an example ready to post. I just need to stop working on these bugs for an hour to finish it up…

  4. Anonymous says:

    Andy Rich did spot a bug in my code and pointed to his related The long-awaited return of DF blog entry:…

  5. Anonymous says:

    Just like String::Split, I dislike those GetFiles methods that put more load on the managed…

  6. Anonymous says:

    Just like String::Split, I dislike those GetFiles methods that put more load on the managed…

  7. Anonymous says:

    Andy Rich did spot a bug in my code and pointed to his related The long-awaited return of DF blog entry:…

  8. Anonymous says:

    Managed C Destructors and Finalizers

  9. Anonymous says:

    Managed C Destructors and Finalizers

Comments are closed.

Skip to main content