I know that everyone and their uncle has blogged about their Dispose pattern flavor of the week, but I feel the need to add my 2 cents to the discussion.

 

I’d like to start with Andrew’s Cardinal Rule of Disposable objects.

 

“If your object has a Dispose method, call it as soon as you are done with the object.”

 

If you follow this simple rule with the Disposable objects that you use in your application, then you will always be ensured that system resources are released as promptly as possible.  You can rest easily tonight.  If you are like me however, just being told to call Dispose isn’t satisfying.  I want to know exactly what is going on “behind the curtain” and more importantly, I want to know why.

 

When the CLR and managed code came onto the scene, there was a great deal of noise made about the Garbage Collector (GC) and how it was going to solve all of the world’s problems (similar musings have been made about other GC languages including Java).  Simple fact of the matter is that the Garbage Collector is very good at what it is designed to do, collecting memory and only memory.  For the host of other resource allocation challenges, the runtime’s GC does not do this for you magically.  What it does do is define a set of patterns to make this as easy for you to manage yourself as possible.

 

The basic “problem” with the GC in this sense, is that it has no knowledge whatsoever of what resources are held by any object beyond the memory space it consumes. 

 

Consider the canonical example which reads in a file with an integer value on each line, adds it to a list of integers, sorts them and then outputs the sorted list to the same file:

 

const string FILEPATH = @"c:\foo.txt";

List<int> data = new List<int>();

StreamReader reader = new StreamReader(FILEPATH);

while (!reader.EndOfStream)

{

      int temp;

      if (int.TryParse(reader.ReadLine(), out temp))

      { data.Add(temp); }

}

data.Sort();

StreamWriter writer = new StreamWriter(FILEPATH);

foreach (int i in data)

{ writer.WriteLine(i); }

 

If you try to run this on your machine it is very likely that you will get an exception on the line:

StreamWriter writer = new StreamWriter(FILEPATH);

The problem is that you already opened that file earlier for read access (the new StreamReader(FILEPATH);) part) and never released it back to the operating system.  When you try to open it again, the OS denies the request because the file is already opened by someone else.

 

We know that the garbage collector is designed to clean up memory when it is no longer used, so it should be able to close the file automatically each time we are done with it (both when we are done reading it in, and when we are done writing it out).  But it doesn’t seem to do that.  Why?  For very good performance reasons (Rico’s article explains these quite well) the GC is “lazy” which means that it won’t clean up memory until it realizes that there is some advantage to doing so.  When you are done with the StreamReader, and trying to open up the StreamWriter to output, there is still plenty of memory available on your machine which is available to your app, so the GC sees no advantage in taking the time to clean up right away.

 

We could force the GC to clean everything up if we wanted to, but that’s a very costly (i.e. time-consuming) process and there are much better ways.

 

WARNING: Do not write code like this or your hair may fall out, your peers will likely laugh at you, and your code will certainly be suboptimal.

 

GC.Collect();

GC.WaitForPendingFinalizers();

 

You could also have used simply:

 

GC.GetTotalMemory(true);

 

Note: for this to work as you might expect, you will need to make sure that you build a release optimized version of your assembly (i.e. without debug settings).  When the runtime encounters a debug assembly, it intentionally prolongs the lifetime of managed objects through the entire scope of a method call to ease debugging.  As a result, the runtime keeps the file open through the entire method, and won’t free it up after it’s last use.

 

This causes the garbage collector to free up all available memory, wait for any finalizers which were triggered by the process (more on this later) and then collect again to get rid of the memory space taken by the finalized objects.  In the process when the garbage collector gets to the StreamReader it closes the file which is then available to be written to.

 

The key point to take away from this is that the GC, because it only tracks memory resources didn’t identify that there was any advantage to cleaning up the StreamReader right away (because there was plenty of memory available) and thus didn’t get around to it.  That’s why it’s possible (though quite unlikely) that if you ran the sample above it would work (if for some unrelated reason the GC decided to collect at an opportune moment for you, say while data.Sort() was executing).

 

So how do we fix the problem?  With Dispose of course.

 

Changing the above sample to this:

 

const string FILEPATH = @"c:\foo.txt";

List<int> data = new List<int>();

StreamReader reader = new StreamReader(FILEPATH);

while (!reader.EndOfStream)

{

      int temp;

      if (int.TryParse(reader.ReadLine(), out temp))

      { data.Add(temp); }

}

reader.Dispose();//<<<<<<<<<

data.Sort();

StreamWriter writer = new StreamWriter(FILEPATH);

foreach (int i in data)

{ writer.WriteLine(i); }

writer.Dispose();//<<<<<<<<<

 

Lets us explicitly say we are done with the file each time and ensures that it is available to be re-opened by the StreamWriter.

 

In C# there is shortcut syntax for this, the using statement:

 

const string FILEPATH = @"c:\foo.txt";

List<int> data = new List<int>();

using (StreamReader reader = new StreamReader(FILEPATH))

{

      while (!reader.EndOfStream)

      {

            int temp;

            if (int.TryParse(reader.ReadLine(), out temp))

            { data.Add(temp); textBox1.Text += temp.ToString() + "\r\n"; }

      }

}//reader is disposed here automatically

data.Sort();

using (StreamWriter writer = new StreamWriter(FILEPATH))

{

      foreach (int i in data)

      {

            writer.WriteLine(i);

            textBox2.Text += i.ToString() + "\r\n";

      }

}//writer is disposed here automatically

 

The using statement ensures that the Disposable object created inside the statement will be disposed upon leaving the code block (delimited by the curly braces) regardless of any exceptions which may be thrown.  This makes it yet more robust than the code which explicitly calls dispose.

 

Simple isn’t it?

 

Next post it will get more complicated, as I will talk about defining a disposable object itself (the other side of the curtain).