Recently, in a tutorial about WeakReferences in C#, we
talked a bit about garbage collection and how the garbage collector
works in .NET. I figured since we already started addressing that stuff,
there is no reason not to delve deeper. And so, today we are going to
take a look at how object finalizers work in C#.
What is an object finalizer? I'm glad you asked! They are essentially
the cleanup functions for classes - when an object is collected by the
garbage collector in .NET, the finalizer gets run, hopefully cleaning up
any unmanaged resources that object may have been holding on to (file
references, window handles, network sockets, etc...). An object
finalizer is in may ways similar to the C++
destructor
, but unlike in
C++, a programmer can never call a finalizer directly (in C++, there is
the delete
operator, and .NET has no such equivalent).
So, first, lets take a look at how to write a finalizer, and then we can
delve into the details on when they are run and other caveats.
class ClassWithFinalizer
{
System.Timers.Timer _SillyTimer;
public ClassWithFinalizer()
{
_SillyTimer = new System.Timers.Timer(100);
_SillyTimer.Elapsed +=
(a, b) => Console.WriteLine("Still Alive!");
_SillyTimer.Start();
}
//Finalizer
~ClassWithFinalizer()
{
_SillyTimer.Stop();
_SillyTimer.Dispose();
Console.WriteLine("You Killed Me!!");
}
}
The class in the code block above has a finalizer, and probably by
looking at the code you have already figured out the syntax for writing
your own. To write a finalizer method, all you do is create a method
with the same name as the class (kind of like how you declare a
constructor) and you prefix it with a "\~". The method takes no
arguments, and it does not use the public/private scoping keywords
(because a finalizer never gets called explicitly in code anyway).
So what is the above class doing, anyway? Well, its kind of silly, but
it shows off the finalizer pretty well. In the constructor we create a
timer, and set it so that every 100 milliseconds it prints out the
statement "Still Alive!". So when we create this class, "Still Alive!"
should print to the console window until the program closes....or at
least that is what it would do if there wasn't a finalizer.
When this object gets garbage collected, it stops and disposes the
timer, and prints out the final message "You Killed Me!!". Below is some
code that causes this behavior to happen, and the corresponding output:
static void Main(string[] args)
{
new ClassWithFinalizer();
System.Threading.Thread.Sleep(500);
GC.Collect();
}
Still Alive!
Still Alive!
Still Alive!
Still Alive!
You Killed Me!!
At the start of the main method, we create an instance of
ClassWithFinalizer
, but we don't assign the resulting reference to
anything. That means that we created the object, but no one is
referencing it, so at any point the garbage collector can come along and
destroy it. We then sleep the main thread for a bit, possibly letting
the instance of ClassWithFinalizer
print out "Still Alive" a few
times, and then we force a garbage collection by calling GC.Collect()
.
The garbage collection notices that no one is referencing the instance
of ClassWithFinalizer
, and so collects it, and in the process executes
the finalizer, killing the timer, and printing out the final message of
"You Killed Me!!"
What if we didn't have the explicit call to
GC.Collect()
, and the
program just sat there? Well, lets take that line out (and add a
Console.Read()
to cause the program to sit there):static void Main(string[] args)
{
new ClassWithFinalizer();
Console.Read();
}
Still Alive!
Still Alive!
Still Alive!
Still Alive!
Still Alive!
Still Alive!
Still Alive!
Still Alive!
Still Alive!
Still Alive!
...
...
...
You Killed Me!!
Who knows how long it would be till the garbage collector finally tried
to collect that instance of
ClassWithFinalizer
? It is quite possible
it wouldn't happen until the program closed.
So does that all make sense? Good, cause it is about to become less
clear. One of the main caveats of finalizers in .NET is that there are
no guarantees about when the finalizer for an object will actually get
run. Unlike in C++, where the destructor gets called as soon as an
object goes out of scope (and if that's not enough the
delete
operator
can trigger the destructor explicitly), finalizers in C# are not
deterministic. A C# finalizer will be called at some point between when
the object is last used and the ending of the program - and you as a
programmer don't know any more than that.
Hey, and if you look even deeper, it gets yet more complicated -
finalizers are not run immediately when the garbage collector gets
around to realizing an object can be collected. The garbage collector
sees that the object has a finalizer and so adds it to the finalization
queue (or f-reachable queue). Eventually, the finalizer method gets run,
and then when the garabge collector realizes that, it finally frees the
object's memory. So using finalizers can actually delay the real
collection on an object for some number of garbage collection cycles.
In a simple example like the one above, everything works as expected.
But when you get into bigger programs, this no-deterministic method of
finalization can get in the way. The creators of .NET realized this, and
so created the
Disposable
pattern and the using
statement, which
gives programmers the ability to do much more deterministic object
disposal. That, however, is a discussion long enough for another
tutorial all on its own.
Comments
Post a Comment