One of my last posts was about IDisposable. (Well regarded by the way, thanks for the feedback ) I should have thougt that covered the topic, but actually there are a few things I missed. And some of them certainly should be mentioned in “The Last Article about IDisposable“.
Reference: [FDG] Framework Design Guidelines, Cwalina, Abrams
To IDispose or not to IDispose?
A friend recently asked me “I hear you, but when should I actually implement IDisposable?”. Couriously my own answer didn’t convince me then. Now I’ll try better…
From the disposable pattern point of view there are only two relevant kinds of resources:
- unmanaged resources (i.e. not managed by the CLR)
- managed, disposable resources (i.e. classes implementing IDisposable)
Regarding unmanaged resources: When are you actually dealing with unmanaged resources? Well, talk about COM interop or P/Invoke and Win32 handles. In these cases I would have a look at the the SafeHandle class. This class does all and more of what any of us could think of. Once the resource is properly wrapped, I deal only with the wrapper which is a managed disposable ressource. Done.
That was easy, wasn’t it? Although unmanaged resources are the canonical justification for the IDisposable pattern (and finalizers for that matter), most developers rarely if ever deal with them directly. Database connection? DbConnection is a managed class. File handle? System.IO.File likewise.
Now for managed resources. When should you implement IDisposable? [FDG] gives two guidelines that in my opinion provide a nearly sufficient answer, thus I added a third one:
- DO implement IDisposable on types containing instances of disposable types. In other words “aggregation” is the keyword.
- CONSIDER implementing IDisposable if the type is likely to have subtypes that might contain disposable types.
- My own addition to that: CONSIDER implementing IDisposable if you lay some kind of infrastructure … (see my last post for an example).
The first guideline is probably the most common case. And it has a pitfall. It says “instances of disposable types”, not “fields of disposable types”. There “Does any of the data members of my class have a type that implements IDisposable?” is actually the wrong question to ask! All the data member declaration tells you is the type of the data member; not the type of the instance it holds. Therefore the correct question would be “May any of the instances the data members of my class reference implement IDisposable?”. So take a hard look at your members of type object or any interface or base classes that would fall under the second guideline.
To finalize or not to finalize?
Most if not all articles about IDisposable present the pattern the way I did: Have the IDisposable.Dispose method call a virtual method Dispose(bool), have the finalizer call the same method. But [FDG] takes a different approach:
- The “Basic Dispose Pattern” only takes the IDisposable interface into account and effectively addresses managed resources. It does not include the finalizer!
- The “Finalizable Types” pattern acts as extension to the former, it adds the finalizer and deals with unmanaged resources.
The rationale for the basic dispose pattern? Well, why call the Dispose(bool) method from a finalizer if no unmanged ressource has to be freed? (And remember that you are not allowed to touch managed resources in the finalizer, as they might already be finalized.) Why pay for the finalizer and add the runtime effort (maintaining the object in the finalization queue) that comes with it in the first place?
At first I had some severe concerns (basically about the increased complexity and the implied risk of missing finalizers) and was inclined to contradict people like Herb Sutter . But actually it really simplifies the picture. The two kinds of resources mentioned above actually match the guidelines:
- Unmanaged resources: SafeHandle implies (and goes beyond) “Finalizable Type“.
- Managed ressouces: “Basic Dispose Pattern” and no finalizer!
The implication is interesting: No need for you to ever write a finalizer!
But Wait! What about these cases:
- [FDG] also talks about “resources that need to be freed explicitely and that do not have finalizers”.
In other words: unmanaged resources (already covered) or managed resources that are probably not implemented correctly (which rather needs a bugfix or workaround).
- My initial concern: Having a class that implements IDisposable and at the same time omits the finalizer would be something unexpected and a surprise for most developers. So what if a derived class suddenly needed the finalizer and the developer wrongly assumed it was already there?
The reason to need a finalizer would have to be an unmanaged resource. This calls for a wrapper class (e.g. SafeHandle) which in turn contradicts the use case.
- What about classes that already implement a finalizer (and don’t have to)?
Don’t worry. If it does the right thing it does nothing. So why bother?
Because it is rather unexpected, I’d like to emphasise the corollary: If you rely on SafeHandle or some similar wrapper class, you’ll never need to provide a finalizer! (This appears to contradict most IDisposable advice, including IDisposable Interface.)
Exceptions, exceptions, 5 cent each!
When .NET was still fresh there was a guideline not to throw exceptions in finalizers, because they would kill the finalizer thread and prevent other finalizers from being called. Early versions also didn’t guarantee that finalizers would be called during shutdown. Today the situation is as follows:
- Objects will be finalized during shutdown by default (since .NET 1.0)
- Exceptions will kill the process rather than the thread (since .NET 2.0). This really improves the situation, as the exception will not stay unnoticed (as before).
- The guideline is that exceptions in finalizers should be limited to critical ones intendend to shut down the application. The guideline for Dispose is similar. ([FDG])
- C# finalizers of derived classes will ensure that the finalizer of the base class is called even in the presence of an exception (see Finalize and Dispose Explained).
The last point is interesting. If you open the code of a class that overrides the finalizer in Reflector and choose C# format it will look like this:
The very same code in VB format however has a slightly different outfit:
All very well, the compiler does the work for us (as long as you are a decent C# developer ). But there still is one gap to fill: What about overriding the Dispose Method? Don’t the same issues apply here? They do. And does the compiler handle that as well? No, it doesn’t. Are we lost then? No, we aren’t.
First of all, I’m telling you how to do it right . Secondly, I convinced Microsoft to check whether you did follow my advice. There is a rule in FxCop and Code Analysis in Visual Studio respectively (CA2215:DisposeMethodsShouldCallBaseClassDispose). Take the following code snippet:
protected override void Dispose(bool disposing)
// free managed resources
// call base
If you enable Code Analysis (I really wish this would be the default) it will generate the following warning:
MSBUILD : warning : CA2215 : Microsoft.Usage : Change DerivedObject.Dispose(Boolean):Void so that it calls its base class Dispose method for all code paths. Place the call in a finally block to ensure that resources are cleaned up when an exception is raised.
As it states, the correct version would be:
protected override void Dispose(bool disposing)
// free managed resources
// call base
Actually this little gem can be found in the documentation (e.g. in Implementing a Dispose Method), although rarely and never really advertised.
To null or not to null?
“Shouldn’t I set the data members to null?”. A question I got more than once in various incarnations.
So, just for the record: There is no need to set data members referencing disposable objects (or any object) to null. It does not help freeing resources and the garbage collector won’t work faster.
It might still be a good idea to set data members referencing disposable objects to null after disposing them. Just as a safety net.
Right about the same time I wrote my initial post, MS decided the topic is worth being adressed. There was an article on MSDN magazine in the CLR Inside Out column. I have to say, *ahaem*, it’s not nearly as complete as my coverage. But it mentions security and I got some hints on SafeHandle and try/finally in Dispose from it (the later one employed but not described, sic!).
Well, I hope THAT covers the topic. Or did I still miss something?