Разница воскресения в использовании Object Initializer

У меня есть этот код:

По сути, я пытаюсь продемонстрировать использование финализатора c # и создать объект, который не может умереть, я назвал его Zombie. Обычно эта демонстрация работает отлично, но сегодня я попытался использовать тот же код с инициализатором объекта, а не просто присваивать свойству (имя в данном случае). Я заметил, что есть разница. А именно, что финализатор никогда не вызывается, даже когда я изо всех сил стараюсь заставить сборщика мусора выполнять свою работу.

Может кто-нибудь объяснить разницу, или я нашел ошибку в компиляторе C #?

(Я использую C # 4 в VS2010 SP1 на Win7x64)

Благодарю.

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);
      }
    }
  }
}
 Jeppe Stig Nielsen21 окт. 2013 г., 23:09
Пробовал это с VS2012 и .NET 4.5. Я не смог найти ни одного случая, когда существовала бы разница между инициализатором объекта и кодом «первый стиль, затем набор свойств» «старого стиля». Если вы делаете это в режиме отладки без оптимизации,GC не будет собиратьz (это объектz изначально ссылка) до завершения метода. Это для облегчения отладки; он не будет собираться, пока локальная переменная не выйдет из области видимости формальным образом (z все еще находится в области видимости в конце метода). Если вы извлекаете первую частьMain (с участиемz) в отдельный метод, он будет работать и при сборке Debug.
 jeroentrappers17 мар. 2012 г., 21:20
Когда я создаю Zombie z дополнительным методом, тогда проблема действительно исчезает. public static Zombie CreateZombie () {return new Zombie {Name = "Guy"}; }

Ответы на вопрос(2)

чтобы объекты не умирали, вам не нужно связываться с финализаторами. Просто создайте личный статический список всех экземпляров и добавьте объекты в этот список по мере их создания:

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

    public Immortal()
    {
       _immortals.Add(this);
    }
}
 jeroentrappers17 мар. 2012 г., 21:17
Я понимаю, что цель демонстрации - продемонстрировать финализатор.
Решение Вопроса

Хотя оригинальный ответ ниже все еще точен, похоже, что это смесь отладочной информации и оптимизации, которая имеет значение здесь.

Из моих экспериментов:

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

Финализатор по-прежнему вызывается на моем компьютере при компиляции в командной строке с/o+, Я предполагаю, что вы работаете в отладчике, который меняет поведение GC. Без отладчика GC будет собирать все, что может доказать, что он никогда не будет прочитан.С участием отладчик, я считаю, что GC не будет собирать объекты, которые все еще имеют ссылки в стеке, даже если неткод читать переменные в вопросе.

Теперь с инициализатором объекта код компилятора включает дополнительную ссылку на стек. Эта строка:

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

эффективно:

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

Назначениеz только выполняетсяпосле все свойства были установлены.

Я думаю, чтоtmp Переменная здесь поддерживает объект в живых.

 Jon Skeet17 мар. 2012 г., 21:21
@jeroentrappers: вы строите в конфигурации Debug или Release? (Я вижу "Финализатор вызван на zombieGuy", если это то, что вы ожидаете, но не видите ...)
 jeroentrappers17 мар. 2012 г., 21:16
Спасибо за чрезвычайно быстрый ответ. Однако, когда я запускаю с отладчиком или без него (F5 или Ctrl-F5), нет никакой разницы. Переменная tmp в стеке действительно объясняет это.
 jeroentrappers17 мар. 2012 г., 21:25
Я действительно собирался в Debug. При сборке в Release проблема исчезает автоматически. Благодарю.
 Jon Skeet17 мар. 2012 г., 21:25
@jeroentrappers: см. мои изменения в верхней части поста.

Ваш ответ на вопрос