I wrote a little bit on Disposable objects and their uses in Chapter 5 of the Performance and Scalability PAG (look here and the following sections) and it triggered a lively discussion here at MS the other day. Here’s the bit of interest and some of the discussion reduced to Q&A form:
“The reason you want to avoid finalization is because it is performed asynchronously and unmanaged resources might not be freed in a timely fashion. This is especially important for large and expensive unmanaged resources such as bitmaps or database connections. In these cases, the classic style of explicitly releasing your resources is preferred (using the IDisposable interface and providing a Dispose method). With this approach, resources are reclaimed as soon as the consumer calls Dispose and the object need not be queued for finalization. Statistically, what you want to see is that almost all of your finalizable objects are being disposed and not finalized. The finalizer should only be your backup.”
Q: The whole point of GC is to manage the lifetime of objects in a sensible way. Doesn’t this “guideline” (using IDisposable) suggest that objects in certain categories (with attached unmanaged resources of any kind) cannot participate in GC – and we require the client to manage it outside the GC apparatus?
A: Yes that is basically correct. Finalizable objects generally require the client to take additional action, such as wrapping their use in a “using” statement. If the client does not do this they will get sub-optimal performance. It is not mandatory, however, so basically what you want to do is Dispose all the ones that can be readily Disposed because they have easily understood lifetime and let the GC handle just the exceptions, which hopefully are much more rare.
Q: Doesn’t this make it much harder to use classes that have a finalizer?
A: That has not been my experience. I have found that generally the developer that creates the class cannot say what the lifetime of instances will be but that it is most often a simple lifetime and that they should therefore provide a facility (Dispose) which allows the customer of their class to take advantage of the common case and get superior performance.
Q: Consider the case where the managed current implementation has an underlying unmanaged implementation, doesn’t that lead to you a design where there are unmanaged resources attached to virtually all objects?
A: This is an exceedingly bad situation to be in. If you truly have finalizable state in all your objects you will swamp the finalizer thread and give the GC fits because all your objects will be living at least one generation longer than they otherwise would have had to live. I would urge you to find a way to consolidate the unmanaged state into comparatively few objects which can then be recycled like (e.g.) database connections or something of that ilk. If your objects are, on the other hand, not transient then you may be ok because recovery of the unmanaged resources is a rare event in any case.
The use of Dispose pattern is not advice that is lightly given. You may be very disappointed with the performance you get if you follow another pattern and are not very careful.