May 17, 2009 16:22
Act I. Development office hall. Two developers, Кастусь and Пятрусь, standing near the coffee machine[1]. Late spring afternoon.
K: Hey man, do you think it's necesssary to call Dispose on datasets?
П: Sure - everything that implements IDisposable needs to be explicitly disposed. Thus you release the unmanaged resources you're probably holding.
K: (exultedly) Fair enough - but DataSet doesn't actually implement IDisposable! When you dispose a dataset you call Dispose on its parent, MarshalByValueComponent. And that guy has only managed dispose logic:
protected virtual void Dispose(bool disposing)
{
if (disposing) {
//some managed disposing code here
}
}
П: Hmm. True, but when you dispose a dataset, you don't call that method. Instead, you call a paramless Dispose() that would also suppress finalization:
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
K: Do you think this makes any difference?
П: Yup. Even though DataSet doesn't have a finalizer, it's parent MarshalByValueComponent does. And types with a finalizer are difficult when it comes to memory management.
When a finalizable object gets created, a reference to it gets added to
a so called finalization queue inside the GC. When your
finalizable object (read: your dataset) is no longer used in the app,
that reference is moved from the finalization queue to another one
called "f-reacheable queue". And each pointer in the latter identifies an object that is ready to
have its Finalize method called.
K: (dispiritedly) And that means even if the object is no longer referenced in the app, it has a reference in the f-reachable queue, right?
П: Absolutely. And its memory can be reclaimed only when the GC is called next
time. So, your finalizable instance gets promoted to the next
generation - and moreover, all objects it references to get promoted as
well.
(Curtain)
***
Act II. Small pub, dusty guitars on the walls and hard rock in the background. Кастусь and Пятрусь drinking beer.
K: (slams his pint on the table) Yikes! And if you call that suppress finalization thing, it gets better?
П: Much better. When you call GC.SuppressFinalize(this) it turns on a bit flag associated with the object. When the flag is on, the CLR doesn't move the reference from the finalization queue to the freachable queue. That means the finalizer will never be called, and you don't have an overhead of promoting the object graph to the next generation.
K: (whistles) That's impressive. But the funny thing is that DataSet calls SuppressFinalize in its constructor.
П: (scratching his head) Well, this breaks the whole chain. If they suppress finalization in the constructor then, although DataSet has a finalizer defined in its parent, it will never get called.
K: Effectively that means, for now, nothing bad will happen if you don't dispose datasets.
П: (hesitantly) Sort of. The only thing is that if you explicitly dispose your dataset, it gets removed from the container component and the memory behind the dataset can be reclaimed. If you don't, the dataset will be garbage collected only when its container is garbage collected.
K: And of course, theoretically it's still possible that some unmanaged stuff would be added in a future version of .net, and if we don't dispose datasets now, we're in trouble.
П: Exactly.
(Two more pints brought in. Curtain)
Footnotes.
- Just in case you're interested, belarusian names Кастусь and Пятрусь are probably written as
"Kastus" and "Pyatrus" (though there could be variations) and
pronounced as [kas'tu:s] and [pjat'ru:s].