6

I have this code:

Essentially i'm trying to demonstrate the use of the c# finalizer and make an object that cannot die, I called it Zombie. Now, normally this demo works great, but today I tried using the same code with the object initializer instead of just assigning to the property (Name in this case). I noticed there is a difference. Namely that the finalizer never gets called, not even when I'm trying my best to make the Garbage Collector do it's work.

Could someone explain the difference, or have I found a bug in the C# compiler?

(I'm using C# 4 in VS2010 SP1 on Win7x64)

Thanks.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Zombie
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Main thread: " + Thread.CurrentThread.ManagedThreadId);

              // case 1: this is where the problem is located.
      Zombie z = new Zombie { Name = "Guy" }; // object initializer syntax makes that the finalizer is not called.

              // case 2: this is not causing a problem. The finalizer gets called.
      //Zombie z = new Zombie();
      //z.Name = "Guy";

      WeakReference weakZombieGuyRef = new WeakReference(z, true);

      z = null;

      GC.GetTotalMemory(forceFullCollection: true);

      GC.Collect();

      while (true)
      {

        Console.ReadKey();
        if (weakZombieGuyRef.IsAlive)
        {
          Console.WriteLine("zombie guy still alive");
        }
        else
        {
          Console.WriteLine("Zombie guy died.. silver bullet anyone?");
        }

        Zombie.Instance = null;

        GC.AddMemoryPressure(12400000);
        GC.GetTotalMemory(forceFullCollection: true);

        GC.Collect();
      }


    }
  }

  public class Zombie
  {
    public string Name { get; set; }
    public  static Zombie Instance = null;

    ~Zombie()
    {
      Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
      Console.WriteLine("Finalizer called on zombie" + this.Name);
      lock (typeof(Zombie))
      {
        Instance = this;

        GC.ReRegisterForFinalize(this);
      }
    }
  }
}
jeroentrappers
  • 130
  • 1
  • 8
  • When I create the Zombie z in an extra method, then the problem indeed goes away. public static Zombie CreateZombie() { return new Zombie { Name = "Guy" }; } – jeroentrappers Mar 17 '12 at 20:20
  • 1
    Tried this with VS2012 and .NET 4.5. I could not find any cases where there was a difference between object initializer and "old-style" create-first-then-set-property code. If you do this in Debug mode without optimizations, the `GC` will not collect `z` (that is the object `z` originally referenced) before the method has ended. That's for easier debugging; it won't collect until local variable is out of scope in a formal way (`z` is still in scope near the end of the method). If you extract the first part of `Main` (with `z`) out to a separate method, it will work under Debug build as well. – Jeppe Stig Nielsen Oct 21 '13 at 21:09

2 Answers2

19

EDIT: While the original answer below is still accurate, it looks like it's the mixture of debug information and optimization which makes a difference here.

From my experiments:

Compiler flags                        Result
/o+ /debug-                           Finalizer runs
/o+ /debug+                           Finalizer runs
/o- /debug-                           Finalizer runs
/o- /debug+                           Finalizer does *not* run

The finalizer is still called on my box, when compiling on the command line with /o+. My guess is that you're running in a debugger - which changes the GC behaviour. Without the debugger, the GC will collect anything that it can prove will never be read. With the debugger, I believe the GC won't collect any objects which still have references on the stack, even if there's no code to read the variables in question.

Now with an object initializer, the compiler code includes an extra reference on the stack. This line:

Zombie z = new Zombie { Name = "Guy" };

is effectively:

Zombie tmp = new Zombe();
tmp.Name = "Guy";
Zombie z = tmp;

The assignment to z is only performed after all the properties have been set.

My guess is that the tmp variable here is keeping the object alive.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks for the extremely quick answer. However when I run with or without debugger (F5 or Ctrl-F5) there is no difference. The tmp variable on the stack would indeed explain it. – jeroentrappers Mar 17 '12 at 20:16
  • @jeroentrappers: Are you building in Debug or Release configuration? (I'm seeing "Finalizer called on zombieGuy" if that's what you're expecting but not seeing...) – Jon Skeet Mar 17 '12 at 20:21
  • I was building in Debug indeed. When building in Release the problem goes away automatically. Thanks. – jeroentrappers Mar 17 '12 at 20:25
2

If you want objects that don't die, you really don't need to mess with finalizers. Just have a private static list of all instances, and add objects to that list as they are created:

class Immortal
{
    static List<Immortal> _immortals = new List<Immortal>();

    public Immortal()
    {
       _immortals.Add(this);
    }
}
zmbq
  • 38,013
  • 14
  • 101
  • 171