Introduction
I see questions regarding the GC on a daily basis. Finalize
vs. Dispose
is a popular topic.
Let's get it in the clear.
All of us .NET programmers make use of the Garbage Collector (GC) � some
without knowing so. You have to familiarize yourself with the internals of the
GC if you wish to create scalable components � there is no other option. Even
though it does all its bits automagically, you can harness a lot of its power by
understanding three basics: Finalize
, Dispose
, and the Destructor protocol in managed code. I will not
be going into the finer details of how the GC does its job, but rather explain
how you as a programmer can (and should) optimize your objects for the GC.
Finalize
When an object is instantiated, the GC allocates memory for it on the managed
heap. If the class contains a Finalize
method, the object
is also enlisted in the �finalisation queue�. When this object is no longer
needed, its memory will be reclaimed (freed) by the GC. If the object is
enlisted in the finalization queue, its Finalize
method
will be called before discarding of the object. The purpose of the Finalize
method is to release any resources (like a database
connection, or a handle on a window) that might be in use by your object.
Since the GC decides when it is best to clean up objects (and it does a damn
fine job in doing so!), you have no way of telling when exactly Finalize
will be called. Finalize
is also a
protected
member and can thus not be called explicitly.
Does this mean that cleaning up your object is left entirely in the hands of the
GC?
Dispose
Of course not. For increased performance, it is best to cleanup your unused
resources immediately after using them. For instance, as soon as you have
retrieved your data through a database connection, the connection should be
discarded of since it eats up system resources like memory, which could be
better utilized by objects that you do in fact need. For this reason, an object
can implement the Dispose
method (by implementing the
IDisposable
interface). Calling the Dispose
method on an object does two things. Firstly, it cleans up any resources that
were in use by your object. Secondly, it marks the object so that the GC would
not call its Finalize
method when it collects it � the
resources have been cleaned up already in your Dispose
method. This way, you save the overhead of the GC�s call to Finalize
, and you can clean up your object at the most
appropriate time.
How to implement Finalize and Dispose
Now that you know the reasons for these two methods, let�s see how to
implement it.
In managed code, you cannot override or even call Finalize
� it is implicitly generated (in IL) by the compiler if
you have a destructor for your object. In other words, the following:
~MyClass
{
}
Translates to the following:
protected override void Finalize()
{
try
{
}
finally
{
base.Finalize();
}
}
As you can see, the method also calls Finalize
on its
parent type, and the parent type will call Finalize
on its
parent type � the whole hierarchy of your object is thus cleaned up. It is
important to understand that you should only have a destructor for your class if
it is really necessary, since calling Finalize
, and
enlisting objects that implement Finalize
in the
finalization queue by the GC, has significant performance implications.
The Dispose
method is publicly callable. (I�ll explain
the overload that accepts the boolean
parameter later).
Here is an example of an object that implements IDisposable
:
public class MyClass : IDisposable, BaseClass
{
bool disposed;
public MyClass()
{
disposed = false;
}
~MyClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
try
{
if (!this.disposed)
{
if (disposing)
{
}
}
disposed = true;
}
finally
{
base.Dispose(disposing);
}
}
}
When you explicitly call Dispose()
on your object, both
managed- and unmanaged resources will be cleaned up. When the GC cleans your
object (instead of you), only unmanaged resources will be cleaned, since the
managed resources will be cleaned up by the GC when necessary.
Final Notes
Don�t reference any managed resources in your Finalize
method (destructor), since Finalizers are not called in any particular fashion,
and the object you reference may thus be disposed of already. In such a case,
your Finalize
method will fail. If you *do* reference any
managed resources downward in your object hierarchy, those objects will not be
finalized with the current GC collection, and performance will suffer.
When calling any method on your object, it is necessary to first check if the
object has been disposed. So a method in MyClass
would look like
this:
public void MyMethod()
{
if (this.disposed)
{
throw new ObjectDisposedException();
}
}
In a further article, I will dive deeper into the GC, and explain the
implications of threading on your Finalize
and Dispose
methods.
(This article is also published on my
blog.)
[Update: Check GC 102 for further
notes on programming for Garbage Collection.]