Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

Another Look At IDisposable

Rate me:
Please Sign up or sign in to vote.
4.82/5 (49 votes)
29 Aug 20035 min read 278.2K   1.5K   133   46
A discussion of the Dispose method, how to use it, when to use it, and problems using it.

Introduction

This is yet another article on the use of the interface class IDisposable.  Essentially, the code that you are going to see here is no different than the code on MSDN or these two excellent articles:

Garbage Collection in .NET by Chris Maunder:
http://www.codeproject.com/managedcpp/garbage_collection.asp

General Guidelines for C# Class Implementation by Eddie Velasquez:
http://www.codeproject.com/csharp/csharpclassimp.asp

What's different is that this article illustrates the functional aspects of:

  • the destructor
  • the Dispose method
  • memory allocation
  • garbage collection issues

In other words, there's lots of articles showing how to implement IDisposable, but very few demonstration of why to implement IDisposable.

How To Implement IDisposable

The salient features of the code below are:

  • Implement the Dispose method of the IDisposable interface
  • Only Dispose of resources once
  • The implementing class requires a destructor
  • Prevent the GC from disposing of resources if they've already been manually disposed
  • Track whether the GC is disposing of the object rather than the application specifically requesting that the object is disposed.  This concerns how resources that the object manages are handled.

And here's a flowchart:

Image 1

Note two things:

  1. If Dispose is used on an object, it prevents the destructor from being called and manually releases managed and unmanaged resources.
  2. If the destructor is called, it only releases unmanaged resources.  Any managed resources will be de-referenced and also (possibly) collected.

There are two problem with this, which I'll come back to later:

  1. Using Dispose does not prevent you from continuing to interact with the object!
  2. A managed resource may be disposed of, yet still referenced somewhere in the code!

Here's an example class implementing IDisposable, which manages a Image object and has been instrumented to illustrate the workings of the class.

public class ClassBeingTested : IDisposable
{
   private bool disposed=false;
   private Image img=null;
   
   public Image Image
   {
      get {return img;}
   }

   // the constructor
   public ClassBeingTested()
   {
      Trace.WriteLine("ClassBeingTested: Constructor");
   }

   // the destructor
   ~ClassBeingTested()
   {
      Trace.WriteLine("ClassBeingTested: Destructor");
      // call Dispose with false.  Since we're in the
      // destructor call, the managed resources will be
      // disposed of anyways.
      Dispose(false);
   }

   public void Dispose()
   {
      Trace.WriteLine("ClassBeingTested: Dispose");
      // dispose of the managed and unmanaged resources
      Dispose(true);

      // tell the GC that the Finalize process no longer needs
      // to be run for this object.
      GC.SuppressFinalize(this);
   }

   protected virtual void Dispose(bool disposeManagedResources)
   {
      // process only if mananged and unmanaged resources have
      // not been disposed of.
      if (!this.disposed)
      {
         Trace.WriteLine("ClassBeingTested: Resources not disposed");
         if (disposeManagedResources)
         {
            Trace.WriteLine("ClassBeingTested: Disposing managed resources");
            // dispose managed resources
            if (img != null)
            {
               img.Dispose();
               img=null;
            }
         }
         // dispose unmanaged resources
         Trace.WriteLine("ClassBeingTested: Disposing unmanaged resouces");
         disposed=true;
      }
      else
      {
         Trace.WriteLine("ClassBeingTested: Resources already disposed");
      }
   }

   // loading an image
   public void LoadImage(string file)
   {
      Trace.WriteLine("ClassBeingTested: LoadImage");
      img=Image.FromFile(file);
   }
}

Why Implement IDisposable?

Let's put this class into a simple GUI driven test fixture that looks like this:

Image 2

and we'll use two simple tools to monitor what's going:

The test is very simple, involving loading a 3MB image file several times, with the option to dispose of the object manually:

private void Create(int n)
{
   for (int i=0; i<n; i++)
   {
      ClassBeingTested cbt=new ClassBeingTested();
      cbt.LoadImage("fish.jpg");
      if (ckDisposeOption.Checked)
      {
         cbt.Dispose();
      }
   }
}

The unsuspecting fish, by the way, is a Unicorn Fish, taken at Sea World, San Diego California:

Image 3

Observe what happens when I create 10 fish:

Image 4

Ten fish took up 140MB, (which is odd, because the fish is only a 3MB file, so you'd think no more than 30MB would be consumed, but we won't get into THAT).

Furthermore, observe that the destructors on the objects were never invoked:

Image 5

If we create 25 fish, followed by another 10, notice what happens to the time it takes to haul in the fish, as a result of heavy disk swapping:

Image 6

This is now taking two seconds on average to load one fish!  And did the GC start collecting garbage any time soon?  No!  Conversely, if we dispose of the class as soon as we're done using it (which in our test case is immediately), there is no memory hogging and no performance degradation.  So, to put it mildly, it is very important to consider whether or not a class needs to implement the IDispose interface, and whether or not to manually dispose of objects.

Behind The Scenes

Let's create one fish and then force the GC to collect it.  The resulting trace looks like:

Image 7

Observe that in this case, the destructor was called and managed resources were not manually disposed.

Now, instead, let's create one fish with the dispose flag checked, then force the GC to collect it.  The resulting trace looks like:

Image 8

Observe in this case, that both managed and unmanaged resources are disposed, AND that the destructor call is suppressed.

Problems

As described above, even though an object is disposed, there is nothing preventing you from continuing to use the object and any references you may have acquired to objects that it manages, as demonstrated by this code:

private void btnRefTest_Click(object sender, System.EventArgs e)
{
   ClassBeingTested cbt=new ClassBeingTested();
   cbt.LoadImage("fish.jpg");
   Image img=cbt.Image;
   cbt.Dispose();
   Trace.WriteLine("Image size=("+img.Width.ToString()+", "+img.Height.ToString()+")");
}

Of course, the result is:

Image 9

Solutions

Ideally, one would want to assert or throw an exception when:

  • Dispose is called and managed objects are still referenced elsewhere
  • methods and accessors are called after the object has been disposed

Unfortunately (as far as I know) there is no way to access the reference count on an object, so it becomes somewhat difficult to determine if a managed object is still being referenced elsewhere.  Besides require that the application "release" references, the best solution is to simply not allow another object to gain a reference to an internally managed object.  In other words, the code:

public Image Image
{
   get {return img;}
}

should simply not be allowed in a "managing" class.  Instead, the managing class should implement all the necessary support functions that other classes require, implementing a facade to the managed object.  Using this approach, the application can throw an exception if the disposed flag is true--indicating that the object is still being accessed after it has technically been disposed of.

Conclusion - Unit Testing

The reason I went through this rigmarole is that I wanted to demonstrate the inadequacies of unit testing.  For example, let us assume that the test class I described above does not implement IDisposable.  Here we have an excellent example of how a single test on a class and its functions will succeed wonderfully, giving the illusion that all is well with a program that uses the class.  But all is not well, because the class does not provide a means for the application to dispose of its managed resources, ultimately causing the entire computer system to bog down in fragmented memory and disk swapping.

This does not mean that unit testing is bad.  It does however illustrate that it is far from a complete picture, and unit testing applications such as NUnit could use considerable growth in order to help the programmer automate more complex forms of unit testing.

And that, my friends, is going to be the topic of the next article.

Downloading The Demonstration Project

I have intentionally left out the "fish.jpg", being 3MB in size.  Please edit the code and use your own JPG if you wish to play with the code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ali BaderEddin31-May-12 3:23
Ali BaderEddin31-May-12 3:23 
QuestionWhat about that memory hogging issue? Pin
jp2code24-Jul-09 5:30
professionaljp2code24-Jul-09 5:30 
AnswerRe: What about that memory hogging issue? Pin
Marc Clifton24-Jul-09 8:01
mvaMarc Clifton24-Jul-09 8:01 
GeneralRe: What about that memory hogging issue? Pin
jp2code24-Jul-09 8:19
professionaljp2code24-Jul-09 8:19 
GeneralRe: What about that memory hogging issue? Pin
Diamonddrake23-Oct-10 10:36
Diamonddrake23-Oct-10 10:36 
QuestionRe: What about that memory hogging issue? Pin
fuaubu__al8-May-11 16:26
fuaubu__al8-May-11 16:26 
GeneralGood article Pin
Donsw16-Jan-09 8:21
Donsw16-Jan-09 8:21 
GeneralMy IDisposable implementation (with extra debug checks) Pin
cpeterso22-Oct-07 12:53
cpeterso22-Oct-07 12:53 
Generalanother IDisposable Pin
sammacitto18-Jun-07 23:55
sammacitto18-Jun-07 23:55 
GeneralGC.SupressFinalize Pin
Deltaoo8-Jun-06 1:25
Deltaoo8-Jun-06 1:25 
GeneralDebugView Pin
rstich3-Mar-05 0:48
rstich3-Mar-05 0:48 
GeneralI'm not able to use the concepts in case of strings Pin
Blue Tender10-Mar-04 21:09
Blue Tender10-Mar-04 21:09 
GeneralRe: I'm not able to use the concepts in case of strings Pin
Marc Clifton11-Mar-04 4:51
mvaMarc Clifton11-Mar-04 4:51 
GeneralRe: I'm not able to use the concepts in case of strings Pin
Blue Tender11-Mar-04 18:07
Blue Tender11-Mar-04 18:07 
GeneralRe: I'm not able to use the concepts in case of strings Pin
Marc Clifton12-Mar-04 3:51
mvaMarc Clifton12-Mar-04 3:51 
GeneralRe: I'm not able to use the concepts in case of strings Pin
icymint38-Aug-06 7:51
icymint38-Aug-06 7:51 
General3MB jpeg x 10 = 140MB. Of course it is. Pin
Steven Campbell23-Feb-04 11:46
Steven Campbell23-Feb-04 11:46 
GeneralRe: 3MB jpeg x 10 = 140MB. Of course it is. Pin
wumpus126-Feb-05 17:04
wumpus126-Feb-05 17:04 
GeneralThe GC doesn't fire on this test Pin
Richard Hellrigl1-Feb-04 0:58
Richard Hellrigl1-Feb-04 0:58 
GeneralRe: The GC doesn't fire on this test Pin
Marc Clifton1-Feb-04 11:48
mvaMarc Clifton1-Feb-04 11:48 
GeneralWhy GC did not collect Pin
mikeperetz14-Sep-03 15:50
mikeperetz14-Sep-03 15:50 
GeneralRe: Why GC did not collect Pin
mikeperetz16-Sep-03 3:23
mikeperetz16-Sep-03 3:23 
GeneralRe: Why GC did not collect Pin
howcheng19-Nov-03 14:43
howcheng19-Nov-03 14:43 
GeneralRe: Why GC did not collect Pin
Marc Clifton20-Nov-03 3:39
mvaMarc Clifton20-Nov-03 3:39 
GeneralRe: Why GC did not collect Pin
Wild (Ka)Yaker14-Apr-06 8:43
Wild (Ka)Yaker14-Apr-06 8:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.