Using the GC
I tackled Garbage Collection last year here and even here. Let me add a little more to the subject, prompted by an emaiI I got from a buddy. If I create sample windows project with a single button on the form and add the following code . . .
private void button1_Click(object sender, System.EventArgs e)
{
TestObj obj = new TestObj();
obj.WriteSomethin( "text" );
GC.Collect();
GC.WaitForPendingFinalizers();
doStuff();
}
private void doStuff()
{
for ( int n = 1; n <= 5; n++ )
{
System.Threading.Thread.Sleep( 1000 ); //simulate long running operation
System.IO.StreamWriter sw = new System.IO.StreamWriter( @"C:\Test.log", true );//append to file
sw.WriteLine( "Iteration " + n );
sw.Close();
}
}
}
public class TestObj : IDisposable
{
~TestObj()
{
AllDone();
}
public void Dispose()
{
AllDone();
}
private void AllDone()
{
System.IO.StreamWriter sw = new System.IO.StreamWriter( @"C:\Test.log", true );//append to file
sw.WriteLine( "TestObj Destroyed " + System.DateTime.Now.ToShortTimeString() );
sw.Close();
}
public void WriteSomethin( string strStuff )
{
System.IO.StreamWriter sw = new System.IO.StreamWriter( @"C:\Test.log", true );//append to file
sw.WriteLine( strStuff );
sw.Close();
}
}
If you run it, click button1, and check out C:\Test.log BEFORE closing the application you'll get this:
text
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5
It looks like our Destructor doesn't fire because no “TestObj Destroyed” message is in the file, even though the method has gone out of scope. Only by closing the application does the Destructor actually fire (and log “TestObj Destroyed 5:03 PM”) -- all this even with our statements:
GC.Collect();
GC.WaitForPendingFinalizers();
Which should “empty the finalizers” queue and trigger our GC. It doesn't trigger TestObj destruction; the important point is that I don't have control over exactly when my object will be collected by the runtime.
Add a second button to the form and place this code in the code-behind:
private void button2_Click( object sender, System.EventArgs e )
{
using( TestObj obj = new TestObj() )
{
obj.WriteSomethin( "text" );
}
doStuff();
}
Run the app and click the second button. “Hold on!“ you might say, it looks like it's disposing multiple TestObj objects because we get multiple “TestObj Destroyed 5:03 PM” messages; not to worry, comment out the explicit c# destructor (the “~TestObj” function) so that our object only writes a message once upon object finalization. Then run the application and check the log file:
text
TestObj Destroyed 5:13 PM
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5
Much better. The more control I have, the happier I am. Our TestObj is destroyed upon the termination of our using() statement scope. Plus, button2_click doesn't explicitly call GC.Collect and GC.WaitForPendingFinalizers -- unless you have a really good reason, it's unwise to call these methods. Letting the .Net Framework manage the memory is usually preferred.
For more on GC, including a bit on WeakReferences, check out the MSDN articles: http://msdn.microsoft.com/msdnmag/issues/1100/gci/
http://msdn.microsoft.com/msdnmag/issues/1200/GCI2/
Happy .Netting