Monday, December 03, 2007

Where Did My Object Go? Part 1

I ran across this scenario a few months ago and was just reminded of it. It takes a bit of an edge case to make it a problem, but it's interesting all the same.

There's a regular idiom in C# in which we call a method on an object instance that we've created inline:

    new MyObject().LongRunningMethod();

There assumption may be an assumption that the lifetime of this instance of MyObject extends at least until LongRunningMethod returns, but this isn't necessarily true. This same assumption is often made about local references to objects:

    MyObject o = new MyObject();
o.LongRunningMethod();

However, in both these cases the object instance may be collected before LongRunningMethod returns.


Does this really happen?


Yes, it can and does. The code below exercises this behavior. When you run it you will see the following output, indicating that the object was collected and finalized before LongRunningMethod returns:


    Using release build
    Inline
    Entering MyObject1.LongRunningMethod().
    Finalizing in ~MyObject1().
    Returning from MyObject1.LongRunningMethod().

    Local reference
    Entering MyObject1.LongRunningMethod().
    Finalizing in ~MyObject1().
    Returning from MyObject1.LongRunningMethod().

Note: You will not see this behavior in a debug build. When running code marked as "debug" the JITter extends the lifetime of the local object to the end of the method. So you will not see this effect if you've compiled with the /debug flag.


Note also that this article also assumes we're using CLR 2.0. Future versions could obviously behavior differently.


Here's the code. Just drop it into test.cs, run csc test.cs, and execute test.exe.

    using System;

sealed class MyObject
{
~MyObject()
{
Console.WriteLine("Finalizing in ~MyObject1().");
}

public void LongRunningMethod()
{
Console.WriteLine("Entering MyObject1.LongRunningMethod().");

// A long-running method can easily experience a garbage collection
// before returning. This one happens for force it to occur.
GC.Collect();
GC.WaitForPendingFinalizers();

Console.WriteLine("Returning from MyObject1.LongRunningMethod().");
}
}

class Program
{
static void Main(string[] args)
{
#if DEBUG
string build = "debug";
#else
string build = "release";
#endif
Console.WriteLine("Using {0} build", build);

// Try it both ways.

Console.WriteLine("Inline");
new MyObject().LongRunningMethod();

Console.WriteLine();
Console.WriteLine("Local reference");
MyObject o = new MyObject();
o.LongRunningMethod();
}
}

Is this a problem?


Generally, I'd say it's not a problem. Once it has begun execution, LongRunningMethod doesn't need the original object reference unless it's making reference to that instance. In that case the GC won't be able to collect the object.


I'll discuss how to make it a problem in Part 2 of this article.


 


Technorati tags: , , ,

No comments: