Answer: What is the lifetime of local instances?


Answer to this poser


I wasn’t sure the answer to this question was observable, so I wrote a short program:

using System;
class Early
{
~Early()
{
Console.WriteLine(“Early Cleaned Up”);
}
}
class Test
{
public static void Main()
{
Early e = new Early();

GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(“Done Waiting”);
}
}


The output from this is

Early Cleaned Up
Done Waiting

In other words, there is no guarantee that a local variable will remain live until the end of a scope if it isn’t used. The runtime is free to analyze the code that it has and determine what there are no further usages of a variable beyond a certain point, and therefore not keep that variable live beyond that point (ie not treat it as a root for the purposes of GC).

Comments (15)

  1. You ruined it! I wanted to answer =)

    However, you didn’t specify that in debug builds, the behavior is different… (not that I would ship debug code to begin with, but some people do).

  2. AndrewSeven says:

    Me too, what Nicholas said 😉

  3. Dennis Forbes says:

    An interesting observation about this is that the unnecessary nulling of scope variables (usually at the end of the scope when someone doesn’t understand, or doesn’t trust, the runtime) can actually extend the lifetime of an object. A mention could be made to System.GC.KeepAlive which ensures the runtime doesn’t optimize the object out of existence until the point KeepAlive is called.

  4. Chuck Jazdzewski says:

    One thing you didn’t mention is that the lock will, most likely, be held considerably longer than expected and might never be released. It will only be released when the finalizer is invoked.

    This pattern was used often in C++ because it has deterministic destructor calls but is not valid in C#. The code should use the IDisposable pattern with using(…) { } instead.

  5. drebin says:

    In the original post, there was a ~Mutex – so I thought that was C++ code. But what you wrote (looks like) C#.

    I thought destructors weren’t supported in .NET? If they are, is that still the syntax (using a ~)????

  6. Nick Parker says:

    Jeffrey Richter covers this concept in his Applied .NET Framework Programming book. I think it’s something like

    class Obj

    {

    ~Obj()

    {

    Console.WriteLine("In destructor");

    }

    }

    Obj o = new Obj();

    Console.WriteLine("Max Generations " + GC.MaxGeneration);

    Console.WriteLine("Gen " + GC.GetGeneration(o));

    GC.Collect();

    Console.WriteLine("Gen " + GC.GetGeneration(o));

    GC.Collect();

    Console.WriteLine("Gen " + GC.GetGeneration(o));

    o = null;

    Console.WriteLine("Collecting Gen 0");

    GC.Collect(0);

    GC.WaitForPendingFinalizers();

    Console.WriteLine("Collecting Gen 1");

    GC.Collect(1);

    GC.WaitForPendingFinalizers();

    Console.WriteLine("Collecting Gen 2");

    GC.Collect(2);

    GC.WaitForPendingFinalizers();

    Console.Read();

  7. Bart Jacobs says:

    My rule 1 of finalization: avoid using it.

    My rule 2 of finalization: all you know is that finalization of object O occurs after all calls of GC.KeepAlive on O (*)

    Rule 2 means that if you never call GC.KeepAlive, then finalization of O might occur immediately after allocation of O—even before the constructor has run! But that’s OK—simply include a GC.KeepAlive call after any operation that must occur prior to finalization.

    Rule 2 allows you to not care about what it means to "use" an object, what kinds of optimizations are possible, what the behavior of the garbage collector is, etc.

    (*) It’s actually slightly more complex, because GC.KeepAlive calls that are caused by finalization do not count, of course. So, the complete rule is: Finalization of O occurs after a point P such that if no finalization (of any object) occurred after P, then no GC.KeepAlive calls on O would occur after P.

  8. Chris says:

    What about a ‘using’. I know there is a ‘struct’ IDisposible impl of this somewhere.

    This gives you the semantix you are want (c++), and saves the finalizer cost. (EVIL+1)

    using (Mutex m = new Mutex(…)) {

    }

    struct Mutex : IDisposible {

    // Dispose impl.

    }

  9. Thong Nguyen says:

    I worked on a lot of threading subsystem in DotGNU/PNET. One of the more interesting featres I discovered while doing so was that mutexes are automatically released if the owner thread finishes. This is different from other synchronization objects like monitors & waithandles.

  10. Peter Ibbotson says:

    I notice that both "Bart Jacobs" & "Jeroen Frijters" (on the question part of this) imply that the finaliser can run before the constructor. I dragged out both Erics "A programmers introduction to C#" and Jeffery Richters "Applied .Net Framework programming" and neither imply this.

    In fact my reading of the C Sharp spec here

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csspec/html/vclrfCSharpSpec_3_9.asp

    has four stages that an object goes through and stage one is:

    When the object is created, memory is allocated for it, the constructor is run, and the object is considered live.

  11. Slobodan Filipovic says:

    To Dennis Forbes

    "An interesting observation about this is that the unnecessary nulling of scope variables (usually at the end of the scope when someone doesn’t understand, or doesn’t trust, the runtime) can actually extend the lifetime of an object."

    Not True.

    Try : Eric’s example with e=null; on end

    in Release mode:

    using System;

    class Early

    {

    ~Early()

    {

    Console.WriteLine("Early Cleaned Up");

    }

    }

    class Test

    {

    public static void Main()

    {

    Early e = new Early();

    GC.Collect();

    GC.WaitForPendingFinalizers();

    Console.WriteLine("Done Waiting");

    e=null;

    }

    }

  12. MBA says:

    Helpful For MBA Fans.

  13. MBA says:

    Helpful For MBA Fans.