The use of IDisposable and finalizers
An explanation of how the Garbage Collector handlesIDisposable
and finalizers.At my work we have lots of code like this in our ASP.NET apps:
class LogonPage : IDisposable
{
public LogonPage()
{
}
~LogonPage()
{
this.Dispose();
}
public void Dispose()
{
}
// rest of code here
}
In other words, a class that implements
IDisposable
with an empty Dispose
method and a finalizer (aka destructor) that calls Dispose
.The intention of this code appears to be to somehow give a "hint" to the Garbage Collector to free the memory associated with this object when the "destructor" is called.
Implementing a finalizer in this case has no benefit at all, in fact, it probably has the opposite effect of what was expected. It will not cause the GC to free the object's memory any earilier than an object not implementing a finalizer and it places additional load on the GC.
There are two problems with the assumption that implementing
IDisposable
and a finalizer will somehow affect the GC to free the memory earlier:1. C# finalizers are not deterministic;
2. Objects with finalizers are kept in memory longer than objects without and require additional GC processing.
Non-determistic finalization
It is a common misconception that a C# destructor is similar to a C++ destructor. In C# the "~" method is often called a destructor, but that's a misnomer. It's not a destructor in the C++ sense. Instead the "~" method is really just an override for
Object.Finalize()
. It was a mistake of the .NET implementors to even use the "~" character to indicate a finalizer.In C++ the destructor is deterministic, in other words, the developer is responsible for allocating and freeing memory and destructor is called at an exact predictable time: e.g. when an object on the stack reaches the end of its lifetime (i.e. when it leaves the scope of the code block) or when an object on the heap is explicitly freed with the
delete
keyword.But .NET finalization is "non-deterministic". That means you don't know when and in what order the GC will call the finalizers and you have no control over when the object will be freed. Unlike with C++ destructors, the programmer has no way of explicitly calling a .NET finalizer.
Generations and finalization
The .NET GC uses a generational algorithm. This means the GC divides the managed heap in three logical sections, or generations, called gen 0, gen 1 and gen 2. Each of these generations have a maximum size, initially 256kb, 2MB and 10MB respectively. New objects are usually allocated on gen 0.
When a new object is allocated and the gen 0 memory is too full to accomodate the new object, the GC will reclaim the memory allocated to unreachable objects by compacting the gen 0 memory. Next the GC promotes all gen 0 objects to gen 1. This empties gen 0 for new objects.
Similarly when the gen 1 heap becomes full, the GC reclaims unused memory and promotes the gen 1 objects to gen 2.
The GC performs collection more frequently on lower generations than higher generations.
The GC frees finalizable objects much later than those wihout finalizers.
When a finalizable object is created, a reference to it is added to the "finalization list". When the GC determines that an object is no longer reachable, but it's in the finalization list, it's not freed immediately. Instead the finalizable object is put on another special queue called the "f-reachable queue" and promoted to the next generation. A special thread called the "finalizer thread" monitors the f-reachable queue and calls the finalizers when necessary. These objects will eventually be freed the next time a gen 1 or 2 garbage collection is performed. Therefore it takes at least two garbage collection cycles to free finalizable objects.
Recommended IDispoable pattern
Implementing
IDisposable.Dispose
has no effect on the GC at all. It's little more than just a standard interface for providing a "Close" method on your class to allow the user to close/unload resources.Use the following "Dispose pattern" when your class uses other managed classes that implement
IDisposable
(note a finalizer is not needed here). This is the most common scenario.class MyClass : IDisposable
{
private bool disposed = false;
private FileStream file;
public MyClass()
{
file = new FileStream(...);
}
public void Dispose()
{
if (!disposed)
{
file.Close();
disposed = true;
}
}
public void Close()
{
Dispose();
}
}
If you have to implement
IDisposable
and a finalizer, use the recommended pattern as described in MSDN.class MyClass : IDisposable
{
private bool disposed = false;
private IntPtr file = IntPtr.Zero; // Unmanaged resource
public MyClass()
{
file = ...; // allocate unmanaged resource
}
~MyClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Close()
{
Dispose();
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// dispose managed resources
}
if (file != IntPtr.Zero)
{
// free unmanaged resource
file = IntPtr.Zero;
}
disposed = true;
}
}
}
Summary
Only implement
IDisposable
if you have a reason to do so, e.g. if your class has member fields that implement IDisposable
so you need to provide a Dispose
method call Dispose
on the member fields.The simplest guideline regarding the use of finalizers is: don't use it at all unless your class directly wraps unmanaged resources (such as a native file handle) and you want to make sure that
Dispose
gets called at some point, which really almost never happens in a typical ASP.NET application. Don't create empty finalizers.A class implementing
IDisposable
doesn't always require a finalizer, but a class that implements a finalizer should always implement IDisposable
.This was just my interpretation of the GC process. For a more complete and accurate description, see Jeffrey Richter's book "CLR via C#".
Also see: