Click here to Skip to main content
Click here to Skip to main content

Another Look At IDisposable

By , 29 Aug 2003
 

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:

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:

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:

Observe what happens when I create 10 fish:

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:

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:

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:

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:

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:

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

About the Author

Marc Clifton
United States United States
Member
Marc is the creator of two open source projets, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberAli BaderEddin31 May '12 - 3:23 
Very neat.
QuestionWhat about that memory hogging issue?memberjp2code24 Jul '09 - 5:30 
Hi Mr. Clifton,
 
In your article above, you wrote:
 
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).
 
I recently ran into the same thing (while loading Data Tables into a Data Grid View control). I quickly exceeded WinXP's 2 GB per user limit, whereas if I simply dumped the data for all of these tables into Excel documents, the total file sizes came out to 25 MB or so.
 
Could you shed some light on what's going on? It doesn't have to be MVP exact verbiage, but I am curious about what is happening.
 

AnswerRe: What about that memory hogging issue?protectorMarc Clifton24 Jul '09 - 8:01 
jp2code wrote:
Could you shed some light on what's going on?

 
My take on it is that DataTables are horribly inefficient, and a DataGridView is probably doing a lot of indexing behind the scenes, as is a DataView, to support fast sorting and filtering. And Excel is probably doing some sort of compression.
 
Marc
 
Will work for food.
Interacx


I'm not overthinking the problem, I just felt like I needed a small, unimportant, uninteresting rant! - Martin Hart Turner


GeneralRe: What about that memory hogging issue?memberjp2code24 Jul '09 - 8:19 
Hi Marc,
 
I use DataTables often. I never heard anyone say they were inefficient. Not knocking your take, just wondering why you say that. You could be dead on!
 
Could you provide some details? If they are that bad, I need to stop using them in my Pocket PC applications!
 
Thanks,
Joe
 

GeneralRe: What about that memory hogging issue?memberDiamonddrake23 Oct '10 - 10:36 
I haven't checked the code, but when a compressed image is loaded into memory, it exists there as a uncompressed image, not the same as its compressed file on disk. Even a small 50kb jpeg image when uncompressed can be a 1-2 mb bitmap in memory.
 
not saying that's the situation here, as I haven't looked at the code, but if the images were loaded with Image.fromfile then this is certainly the case as that method always returns a bitmap image.
QuestionRe: What about that memory hogging issue?memberfuaubu__al8 May '11 - 16:26 
Image expansion is most certainly an issue here. For example, loading a 3.27MB jpeg into Photoshop expands it out to 28.7MB (about 8.5 times). Each click on the create25 button is asking for 695MB of memory.
 
Incidentally, why not get around the issue by keeping the runtime informed? For example, just add
 
GC.AddMemoryPressure();
 
with an estimate of the image file size as the last line of LoadImage()? It appears to work in tests - the GC gets stuck in pretty quickly.
GeneralGood articlememberDonsw16 Jan '09 - 8:21 
Good article. It does show the basis of idispose. the referance should still be left to the programmer and unit test as you outlined.
GeneralMy IDisposable implementation (with extra debug checks)membercpeterso22 Oct '07 - 12:53 
Here is my take on implementing IDisposable, including extra debug code to find "leaked" objects that have not been disposed and derived classes forgot to call base.Dispose(disposing):
 
http://www.cpeterso.com/gems/IDisposable.html
 
Chris Peterson
Generalanother IDisposablemembersammacitto18 Jun '07 - 23:55 
///
/// Method for cleanning up cosmetic memory usage
/// This for ensure the application always use small memory for life time
///

/// System.Diagnostics.Process.GetCurrentProcess().Handle
/// -1
/// -1
///
[System.Runtime.InteropServices.DllImport("Kernel32", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int maximumWorkingSetSize);
//private extern static Boolean CloseHandle(IntPtr handle);
 
// 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 finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
///
/// Method for Cleanup Resource
///

///
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if(!this.disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if(disposing)
{
// Dispose managed resources.
component.Dispose();
}
// Release unmanaged resources. If disposing is false,
// only the following code is executed.
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
// Note that this is not thread safe.
// Another thread could start disposing the object
// after the managed resources are disposed,
// but before the disposed flag is set to true.
// If thread safety is necessary, it must be
// implemented by the client.
 
}
disposed = true;
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
///
/// IDispose Method
///

public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
 
in the destructor area u can use Dispose(false)
 
this way will be free your memory alot, i got this code from someone also
just try to help
GeneralGC.SupressFinalizememberDeltaoo8 Jun '06 - 1:25 
Hi. Thanx for the article.. One quick question.. the following code block..
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)
{
1. 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");
}
}
 
Is it valid to call the GC.SuppressFinalize(this); where it is.
if the code fails (for what ever reason) on the line line marked 1. The local variable will not be set and the resources not cleared up, BUT the GC is told to supress it.
 
Thanx

GeneralDebugViewmemberrstich3 Mar '05 - 0:48 
Did the output for the sample come from the Debug Window or DebugView.
I ran DebugView and didn't see anything relating to app running. At least form what I could see.
 
56 29.97971393 [3636] ClassBeignTested: Constructor
.....
....
...

GeneralI'm not able to use the concepts in case of stringssussBlue Tender10 Mar '04 - 21:09 
(I'm using .Net framework 1.0 on Win2K)
 
I have implemented a pop3 application in ASP.Net.. which downloads mail from a POP server and updates the DB on click of a button in the page.
 
Whenever i download large mails (of 5 MB or more) ...the memory consumption of the process 'aspnet_wp.exe' increases dramatically (like 300 MB, which is greater than 60% of the available RAM - 512MB) and is finally recycled by the OS... which results in the download being aborted.
 
Below i have given the piece of code which actually retrieves the data from the POP server. Please review it suggest me as to why it happens. The memory consumption increases when the processing enters this piece of code.
 

int BUFFERSIZE = 100000;
char [] buffer = new char[BUFFERSIZE];
int len;
string strb = "";

try
{
string prevcur,szTemp,prev;
szTemp = RdStrm.ReadLine();
prevcur = prev = szTemp+"\r\n";
was_pop_error(szTemp);
if(!error)
{
while(prevcur.IndexOf("\r\n.\r\n") == -1)
{
strb += prev;
len = 0;
len = RdStrm.Read(buffer,0,BUFFERSIZE);
string cur = new System.Text.StringBuilder("").Append(buffer,0,len).ToString();
prevcur = prev+cur;
prev = cur;
}
}

prevcur = prevcur.Replace("\r\n.\r\n","");
strb += prevcur;

return(strb);
}
catch(InvalidOperationException err)
{
return("Error in mail download" + err.ToString ());
}

 
Vikram S

GeneralRe: I'm not able to use the concepts in case of stringseditorMarc Clifton11 Mar '04 - 4:51 
I would suggest using the string builder for prevcur and cur. The line "prevcur=prev+cur" will probably be very inefficient as it results in .NET creating a new string, rather than expanding the current one.
 
Marc
 
Microsoft MVP, Visual C#
GeneralRe: I'm not able to use the concepts in case of stringssussBlue Tender11 Mar '04 - 18:07 
Marc Clifton wrote:
I would suggest using the string builder for prevcur and cur.
 
I tried using the stringbuilder, but the memory consumption remains the same. For a mail of size 3MB, the memory consumption of aspnet_wp.exe process increases from abt 50Mb to 150 MB when executing these lines of code.
 
Also the mempory is never released when the download is complete even though i have inherited the class from IDisposable and doing all that is necessary in the dispose method. I had commented the line, for testing, where the stingbuilder is used to append the data obtained from the stream and found that the aspnet_wp.exe process never consumes anymore than 55MB while executing these lines of code.
 

 

 
Vikram
GeneralRe: I'm not able to use the concepts in case of stringseditorMarc Clifton12 Mar '04 - 3:51 
Blue Tender wrote:
I tried using the stringbuilder, but the memory consumption remains the same. For a mail of size 3MB, the memory consumption of aspnet_wp.exe process increases from abt 50Mb to 150 MB when executing these lines of code.
 
Hmmm. I'm out of ideas. Try posting this problem on the ASP.NET forum. Maybe a couple other ASP.NET sites as well.
 
Marc
 
Microsoft MVP, Visual C#
GeneralRe: I'm not able to use the concepts in case of stringsmembericymint38 Aug '06 - 7:51 
you are using the strings temporarily, they get re created with each append (+). these are not class members and thus they constantly create garbage that does not dealt with by implementing IDisposable.
 
what you should have done is use StringBuilder extensively rather than temporarily.
StringBuilder.ToString(), should have only been called in the return statement.
 
use string builders instead of strings for prev, curr and
 
this is what focussed programmers do

General3MB jpeg x 10 = 140MB. Of course it is.memberSteven Campbell23 Feb '04 - 11:46 
I can explain that...a 3MB jpeg is compressed, and once loaded into memory it is represented as a bitmap, i.e. much more memory. In your case, it looks like JPEG compression crunched it down to about 20-25% of its original size, which is consistent with what JPEG is capable of.
 
If you rerun your tests with a 3MB .bmp file, you might see something closer to what you expected.
 
I'm sure it wasn't keeping anyone up at night, but I thought I'd mention it for all the obsessive types Smile | :)
GeneralRe: 3MB jpeg x 10 = 140MB. Of course it is.memberwumpus126 Feb '05 - 17:04 
LOL I was just about to post the same thing. Wink | ;)
 
JPEG = compressed. In memory, it's uncompressed! As Steve points out, to see the REAL amount of memory needed, save this as an uncompressed file format. It might even be a bit larger depending on the capabilities of the object-- alpha blending requires an alpha bit, etc.
GeneralThe GC doesn't fire on this testmemberRichard Hellrigl1 Feb '04 - 0:58 
On my machine (Win2k, 256MB RAM, .NET-Framework 1.1) the test has another kind of problem:
the GC doesn't fire at all (unless it is called explicitely).
Even if the machine runs out of memory the GC doesn't fire.
Therefore the Reason of the Load-Image-Delay is the disk-swapping and not the GC.
It may be that the GC fires only if a certain amount of managed Memory was allocated;
so I modified the test adding some menaged-memory allocation (strings):
 
private System.Collections.Specialized.StringCollection sc;
 
private void Create(int n)
{
for (int i=0; i
GeneralRe: The GC doesn't fire on this testeditorMarc Clifton1 Feb '04 - 11:48 
Richard Hellrigl wrote:
Now the GC fires and even witout the call of Dispose the application behaves well.
 
Now that's wierd!
 
Thanks for the feedback. It seems a complicated issue.
 
Marc
 
Latest AAL Article
My blog
Join my forum!
GeneralWhy GC did not collectmembermikeperetz14 Sep '03 - 15:50 

This is now taking two seconds on average to load one fish!  And did the GC start collecting garbage any time soon?  No!  
 
I agree with you but WHY ?
 
There is only one true reference to image; all the other references are not accessible. Do you know the real reason behind this?
 


GeneralRe: Why GC did not collectmembermikeperetz16 Sep '03 - 3:23 
I did some more tests, and finally got the GC to collect on its own only when my machine was starting to run out of memory (this happened after creating the image about 10,000 times).
 

GeneralRe: Why GC did not collectmemberhowcheng19 Nov '03 - 14:43 
I'm not a Windows forms programmer, so this is just a guess, but my thinking is that the GC doesn't come around to collect those until it's necessary (as when you started to run out of memory), or perhaps when you exit the program. In ASP.NET the GC comes around after the page is destroyed, which is right after you've sent it off to the browser.
GeneralRe: Why GC did not collecteditorMarc Clifton20 Nov '03 - 3:39 
howcheng wrote:
but my thinking is that the GC doesn't come around to collect those until it's necessary (as when you started to run out of memory), or perhaps when you exit the program. In ASP.NET the GC comes around after the page is destroyed, which is right after you've sent it off to the browser.
 
Both are correct. The GC doesn't come around until it's necessary or when the app exits. But there is also, I believe, some intelligence, in that, if .NET detects that the system is idle, it starts doing cleanup on its own.
 
What bugs me about this is that it's not really under my control. It's like the cartoon where the pilot hits the "landing gear down" button, and the computer responds with "please wait for garbage collection".
 
The reason the GC comes around after the page is destroyed is because literally the web application is effectively exited and isn't reloaded until a post-back occurs. But that's a good point--in ASP.NET, the issues of GC are less of a potential problem.
 
Marc

 
Latest AAL Article
My blog
Join my forum!
GeneralRe: Why GC did not collectmemberWild (Ka)Yaker14 Apr '06 - 8:43 
"in ASP.NET, the issues of GC are less of a potential problem."
 
However, that is not always true. We have developed a system that is all ASP.NET and we are having major memory problems. (See below)
 
Also you mentioned, "that it's (GC) not really under my control". You can however call System.GC.Collect(); or System.GC.Collect(2); and that will run the GC. If you want to just call System.GC.Collect(0); it will clean up the generation 0 memory and leave all others.
 
The problems that we have has is that we run out of memory and the GC does not run in time to free the memory up before it just stops responding altogether. We have something like 6 gigs of memory and 4 processors. The GC allows the memory to get close to full then tries to free up memory just in time to lock up IIS. Sweet!
 
What we have done until we can make some changes is to restart IIS ever 3 hours. I know sweet!!! But temporarily that is what has to be done. Next we are going to try and call System.GC.Collect(); every hour instead of restarting IIS every 3 hours.
 
The rub is that we have destructors on everything but they don't seem to be called until GC runs. We run started running Dispose() now on several different objects. We will see how that works.
 
Thanks to everyone that posted!! And thanks for the article!

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 30 Aug 2003
Article Copyright 2003 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid