In a previous post I posed a question regarding how garbage collection operates on unused objects. Basically, once there are no more references to an object (let's say "x"), .NET sees that x is no longer used and marks it as available to be freed by the GC. An object's lifetime is based on its usage, not on the scope of the object. If it is not used, it is fair game for the GC to come and grab it. One clarification Stephen Toub and David Kean commented on is that this only applies if a debugger is attached while it is running. Also, John Wood had some good ideas for a demonstration that I borrowed and built on. Thanks guys!


The JIT changes some optimizations to aid in debugging, and this particular optimization is only applied for release builds (or more specifically when an optimized compilation is used; you would see the same behavior if you selected the default Debug configuration but then changed Optimize Code to true in the project’s properties, or used /o+) and only when the debugger is not attached at startup. Try running in release build from the IDE using F5 and then ctrl-F5 (start, and start without debugging) to see. … Stephen Toub

I decided to put some Console app examples together to demonstrate how this works and to try out a few variations. The results were interesting as I found out I was incorrect in my statement where I said that setting x = null at the end of the method would prevent x’s object from being collected by the GC. It didn’t happen in my tests. In the tests I ran, setting x = null did not prevent the GC from collecting it earlier on.

I modified the following code sample from my previous post to add in the details and some messages to help demonstrate the behavior. To test this code, I compiled a release build and executed it form the command window so I could watch it unfold via Console.Writeline messages. The idea behind these examples is that the destructors will write messages when their respective objects are collected. To make sure the Garbage Collector runs right away I issue the GC.Collect method immediately following the x.GetWidth method’s execution. If you want to try this out, grab this code and throw it in a class in a C# Console App. Then make a release build and run it. (You’ll have to rebuild for each test, of course)

Test #0 … when you run the code sample below the destructor for Class2 executes since it is no longer used. Then, once methods have run their course, Class3’s destructor executes.

Test #1 … I uncommented comment #1 (x = null;) to see if the GC would NOT collect Class2. I expected it to keep it around however the GC proved me wrong and still cleaned up Class2 earlier (the same behavior as the Test #0).

Test #2 … I commented comment #1 and uncommented comment #2 (string s = x.ToString();). The GC did not clean up Class2 until after the foo method.

Test #3 … I commented comment #2 and uncommented comment #3 (GC.KeepAlive(x);). The GC did not clean up Class2 until after the foo method.

<P></P> <FIELDSET><LEGEND>GC Test in a Console App</LEGEND>using System;
namespace GarbageCollectionTest
{
class Class1
{
     [STAThread]
     static void Main(string[] args)
     {
         foo();
         Console.ReadLine();
     }

     public static void foo()
     {
         Class2 x = new Class2();
         Class3 y = new Class3();

         y.Width = x.GetWidth();
         Console.WriteLine(“Collecting Garbage”);
         GC.Collect();
         y.Height = y.Width;
         y.Color = “Red”;
         <SPAN class=myComment>//x = null;// #1

         <SPAN class=myComment>//string s = x.ToString(); // #2</SPAN>
         <SPAN class=myComment>//GC.KeepAlive(x); // #3</SPAN>
     }

     ~Class1( )
     {
         Console.WriteLine( “Destructor for “ + this.GetType().Name) ;
     }
}

class Class2
{
     public int GetWidth()
     {
         return 7;
     }

     ~Class2()
     {
         Console.WriteLine(“Destructor for “ + this.GetType().Name) ;
     }
}

class Class3
{
     public int Width;
     public int Height;
     public string Color;

     ~Class3()
     {
         Console.WriteLine(“Destructor for “ + this.GetType().Name) ;
     }
}

}
</SPAN></FIELDSET>