I was looking through some archives recently and came across a couple of documents I wrote way back in 2002 for a project shortly after .NET 1.0 went RTM. Both provide good background information for some topics I want to cover later. So, I thought I'd go ahead and post them for your enjoyment. There's nothing too revelatory here, but if you are getting started with ASP.NET and SharePoint, you should take the time to make sure you understand this topic.
Introduction
Developers using C#, or any CLS language, must take special care when writing code that uses finite system resources or resources that require special work to clean up when no longer needed. This document defines the terms required to discuss the problem and provides a general overview of one way to deal with the issue.
Differences between .NET and COM
Before discussing the details of resource management, consider the differences between .NET components and COM components.
COM manages resources through a process known as reference counting. When a new COM object is created by code running under COM, the operating system makes an internal note that the new object has one other object (the one that did the creation) referencing it. Each time the new object acquires a new reference, for example by being passed to another function, the operating system increases the reference count.
As object references are released, the operating system decreases the reference count. When a COM object's reference count reaches zero, the object is destroyed and the object frees any resources it was using.
For example:
Sub Foo()
Dim myObject as SomeObject
Set myObject = New SomeObject 'The object is created and it's reference count is 1.
Call Foo2(myObject)
Set myObject = Nothing 'The object is rleased and it's reference count is 0.
End Sub
Sub Foo2(myObject as SomeObject) 'The object is passed to a function
'and it's reference count is 2.
.
.
.
End Sub 'The function ends and the reference count is 1.
By tracing the sample code, you can see exactly when the object's reference count reaches zero. When this happens, the object is destroyed. If required, the object executes logic at the time of its destruction to release any critical resources it may be using.
The key point is that traditional COM allows the developer to know exactly when an object will be destroyed.
.NET does things a bit differently.
The .NET Common Language Runtime, CLR, manages memory for applications. The CLR provides an automatic service called a garbage collector that periodically searches memory for objects that are no longer used and frees those it finds. By default, a .NET developer has no way to know when this will happen or the order in which objects found by the garbage collector are released.
Fortunately, that does not prevent a .NET developer from writing code that conserves resources or does required cleanup. It simply requires a different approach.
Garbage Collector
The .NET Framework's garbage collector manages the allocation and release of memory for your application. Each time you use the new operator to create an object, the runtime allocates memory for the object from the managed heap. As long as address space is available in the managed heap, the runtime continues to allocate space for new objects.
However, memory is not infinite.
Eventually the garbage collector must perform a collection in order to free some memory. The garbage collector's optimizing engine determines the best time to perform a collection, based upon the allocations being made. When the garbage collector performs a collection, it checks for objects in the managed heap that are no longer being used by the application and performs the necessary operations to reclaim their memory.
This algorithm used to decide if an object should be garbage collected is considerably more sophisticated than the reference counting scheme used by COM.
Consider this example:
aForm
|_
| simpleProperty
|_
complexCollectionObject
|_
| parentFormProperty = aForm
|_
bunchOfCollectionObjects
There is a form object named aForm that contains a complexCollectionObject that has an array of other objects AND a parentFormProperty that has a reference that points back to aForm.
When some other code, perhaps a different form, opens aForm and the form's complexCollectionObject is created, the reference count to the aForm object is 2. It's being referenced by the code that created it and by an object that it contains. When the code that created the form ends or drops its reference to aForm, it is no longer possible for the program to navigate to either the object instance or any of its properties. Unfortunately, the reference count of the form is still equal to 1 because of complexCollectionObject.parentFormProperty.
If this example were a COM program, the result would be a memory leak. Unless the developer took special care to release the second reference, the memory consumed by the objects would not be released until the program ended or leaked enough memory to crash.
The .NET garbage collector is smart enough to know that aForm is no longer reachable by the application and will release these objects when it runs.
Destructors
Destructors are used to destruct instances of classes.
A class can only have one destructor.
Destructors cannot be inherited or overloaded.
Destructors cannot be called. They are invoked automatically.
A destructor does not take modifiers or have parameters. For example, the following is a declaration of a destructor for the class MyClass:
~ MyClass()
{
// Cleanup statements.
}
The destructor implicitly calls the Object.Finalize method on the object's base class. Therefore, the preceding destructor code is implicitly translated to:
protected override void Finalize()
{
try
{
// Cleanup statements.
}
finally
{
base.Finalize();
}
}
This means that the Finalize method is called recursively for all of the instances in the inheritance chain, from the most derived to the least derived.
The programmer has no control on when the destructor is called because the garbage collector determines this. The garbage collector checks for objects that are no longer being used by the application. It considers these objects eligible for destruction and reclaims their memory.
Destructors are also called when the program exits.
THE PROBLEM WITH DESTRUCTORS
There are a couple of problems with destructors that present a challenge to the developer. The first one is that you have no control over when they will be called. This is a problem if you have some resource in use that you want to cleanup right this second. It is possible to force garbage collection to occur by using the GC.Collect method. Forcing garbage collection causes every pending destructor to execute. However, doing this can have a serious effect on performance.
It is also serious overkill if you only need to release the resources of a single object.
The second problem is more subtle.
When the garbage collector is going through the process of calling destructors, in what order are the destructors called? If there are 50 objects waiting for cleanup, does it destroy them in the order in which they were created? The answer is, "you don't know."
Because of this, it is not safe to reference any other managed objects in the body or call stack of a destructor. The object you are trying to reference could be garbage collected already. If it was, your destructor will produce an unhandled exception and your program will stop running!
At this point you may be wondering why, given the above, you would ever want to write a destructor. The key term in the above paragraph is managed. Generally speaking, managed resources are other .NET objects that the CLR controls. However, resources created with the Windows API, COM objects, and mainframe queues exist outside of the CLR's control. Because these types of resources exist outside of the control of .NET you need destructors to ensure they get cleaned up.
Fortunately, there is an agreed upon standard that .NET developers can use to clean up critical managed resources as needed and that allows you to guarantee that unmanaged resources are always disposed of properly.
iDisposable()
The .NET framework includes an interface called IDisposable. The IDisposable interface defines a single method, void Dispose(). Classes that implement this interface advertise to the outside world that they contain resources that require special cleanup. Code that creates an object from a class that exposes the interface is expected to call Dispose when the object is no longer needed.
The following function uses a framework class called Graphics. Graphics resources are finite and should be conserved when possible. Therefore, the Graphics class provides a dispose method.
private Bitmap _GetBitmap()
{
//Make the bitmap by bit blasting the drawing areas dc to a new dc.
Graphics gSource = _FormToPrint.CreateGraphics();
Bitmap bmCopy = new Bitmap(_FormToPrint.ClientSize.Width,
_FormToPrint.ClientSize.Height, gSource);
Graphics gDest = Graphics.FromImage(bmCopy);
IntPtr dc1 = gSource.GetHdc();
IntPtr dc2 = gDest.GetHdc();
BitBlt(dc2, 0, 0, _FormToPrint.ClientSize.Width,
_FormToPrint.ClientSize.Height, dc1, 0, 0, 13369376);
gSource.ReleaseHdc(dc1);
gDest.ReleaseHdc(dc2);
gSource.Dispose();
gDest.Dispose();
return bmCopy;
}
You can test to see if an object supports IDisposable at runtime using a variety of techniques. The most straightforward is to use the "is" operator.
if(someObject is IDisposable)
{
someObject.Dispose()
}
However, you will generally only need to perform this test if you are using something whose specific type is unknown at design time, like the elements in a dictionary collection for example. If you know a class uses Dispose, make sure you call it when you are done! The easiest ways to discover this is with intellisense or the object browser.
The Problem with Dispose
There are two problems with Dispose methods, or any method that cleans up critical resources.
The first is that you expect the people using your class to be responsible and clean up after themselves. If you believe that people can be trusted to do this every time, I know a used-car salesman who would love to meet you!
Luckily you can help those who seem so unwilling to help themselves. The destructor is guaranteed to run eventually. Because of this, you can call Dispose from the destructor like so:
//This class is implementing IDisposable
public
class DisposeExample : IDisposable
{
//This is a managed .NET class and the GarbageCollector supports it.
public SomeDotNetClass managedObject = new SomeDotNetClass();
//This is some other, unmanaged resource and the
//GarbageCollector DOES NOT support it.
public SomeUnmanagedResource unmanagedObject = new SomeUnmanagedResource();
//This is the implementation of IDisposable.Dispose
// Do not make this method virtual.
// A derived class should not be able to override this method.
public
void Dispose()
{
unmanagedObject.CleanUpMethod();
}
//This is the destructor.
~DisposeExample()
{
this.Dispose();
}
}
What if the managed object also included a dispose method? Since you are a good developer, you need to support calling its dispose method when your dispose method gets called. However, if you were paying attention earlier, you know that changing our Dispose method to look like this:
//This is the implementation of IDisposable.Dispose
// Do not make this method virtual.
// A derived class should not be able to override this method.
public
void Dispose()
{
unmanagedObject.CleanUpMethod();
managedObject.Dispose(); //Unhandled exception waiting to happen.
}
would be bad! Remember, you have no control over the order the destructors are called! If Dispose() is called by the destructor, it is possible that managedObject is already gone. If it is, the user will receive a message box, followed by a trip to the desktop, followed by a call to the help-desk, etc….
What you need is a way to know when Dispose is being called directly instead of by the destructor. You provide this by overloading Dispose to accept a flag that tells us what we need to know. Change the base implementation of Dispose to call the overloaded method indicating Dispose was called directly and modify the destructor to call the overloaded method indicating the garbage collector is running.
//This class is implementing IDisposable
public
class DisposeExample : IDisposable
{
//This is a managed .NET class and the GarbageCollector supports it.
public SomeDotNetClass managedObject = new SomeDotNetClass();
//This is some other, unmanaged resource and the
//GarbageCollector DOES NOT support it.
public SomeUnmanagedResource unmanagedObject = new SomeUnmanagedResource();
//This is the implementation of IDisposable.Dispose
public
void Dispose()
{
//Note that Dispose was called directly.
this.Dispose(true);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the destructor and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected
virtual
void Dispose(bool disposing)
{
//Always clean up unmanaged resources.
unmanagedObject.CleanUpMethod();
if(disposing)
{
//We know that Dispose was called directly and it is safe
//to dispose other managed objects!
managedObject.Dispose();
}
}
//This is the destructor.
~DisposeExample()
{
//Note that Dispose is being called by the destructor.
this.Dispose(False);
}
}
The second problem is easier to understand and to solve. The current example doesn't prevent something calling Dispose more than once. In fact, it is also a runtime error waiting to happen. If someone calls Dispose, sooner or later the destructor will run. If unmanagedObject.CleanUpMethod(); is the sort of operation that can only be done once safely, it could produce an error.
There is also nothing to protect anybody from accidentally calling Dispose more than once.
The example needs two more features. The first is a way to tell the garbage collector that it shouldn't call the destructor. The second is to add an internal flag to keep Dispose from doing its job more than once.
//This class is implementing IDisposable
public
class DisposeExample : IDisposable
{
//Private internal flag used to keep dispose from running more than once.
bool _disposed;
//This is a managed .NET class and the GarbageCollector supports it.
public SomeDotNetClass managedObject = new SomeDotNetClass();
//This is some other, unmanaged resource and the
//GarbageCollector DOES NOT support it.
public SomeUnmanagedResource unmanagedObject = new SomeUnmanagedResource();
//This is the implementation of IDisposable.Dispose
public
void Dispose()
{
//Note that Dispose was called directly by some other object.
this.Dispose(true);
// Take yourself off of the Finalization queue
// to prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the destructor and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected
virtual
void Dispose(bool disposing)
{
//Check to see if it's already been done.
if(!_disposed)
{
_disposed = true;
//Always clean up unmanaged resources.
unmanagedObject.CleanUpMethod();
if(disposing)
{
//We know that Dispose was called directly and it is safe
//to dispose other managed objects!
managedObject.Dispose();
}
}
}
//This is the destructor.
~DisposeExample()
{
//Note that Dispose is being called by the destructor.
this.Dispose(False);
}
}