2012-03-17 10 views
5

tengo este código:diferencia resurrección en el uso de inicializador de objeto

Esencialmente estoy tratando de demostrar el uso de la C# finalizador y crea un objeto que no puede morir, llamó zombi. Ahora, normalmente esta demostración funciona muy bien, pero hoy traté de usar el mismo código con el inicializador de objetos en lugar de simplemente asignar a la propiedad (Nombre en este caso). Noté que hay una diferencia. A saber, el finalizador nunca se llama, ni siquiera cuando hago mi mejor esfuerzo para que el recolector de basura haga su trabajo.

¿Alguien podría explicar la diferencia, o he encontrado un error en el compilador de C#?

(estoy usando C# 4 en el SP1 de VS2010 en Win7x64)

Gracias.

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); 
     } 
    } 
    } 
} 
+0

Cuando creo el Zombie z en un método adicional, entonces el problema desaparece. public static Zombie CreateZombie() { return new Zombie {Name = "Guy"}; } – jeroentrappers

+1

Intenté esto con VS2012 y .NET 4.5. No pude encontrar ningún caso donde haya una diferencia entre el inicializador de objetos y el código "antiguo" de creación-primer-luego-conjunto-propiedad. Si haces esto en el modo de depuración sin optimizaciones, el 'GC' no recogerá' z' (que es el objeto 'z' referenciado originalmente) antes de que el método haya terminado. Eso es para una depuración más fácil; no se recogerá hasta que la variable local esté fuera del alcance de una manera formal ('z' todavía está en el alcance cerca del final del método).Si extraes la primera parte de 'Main' (con' z') en un método separado, funcionará también en Debug build. –

Respuesta

16

EDIT: Si bien la respuesta original a continuación sigue siendo exacta, parece que es la mezcla de la información de depuración y optimización que hace una diferencia aquí.

De mis experimentos:

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

El finalizador todavía se llama en mi caja, cuando se compilan en la línea de comandos con /o+. Supongo que estás ejecutando un depurador, lo que cambia el comportamiento del GC. Sin el depurador, el GC recopilará todo lo que pueda demostrar que nunca será leído. Con el depurador, creo que el GC no recogerá ningún objeto que todavía tenga referencias en la pila, incluso si no hay código para leer las variables en cuestión.

Ahora con un inicializador de objetos, el código del compilador incluye una referencia adicional en la pila. Esta línea:

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

es efectivamente:

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

La asignación a z sólo se realiza después de todas las propiedades se han establecido.

Supongo que la variable tmp aquí mantiene el objeto vivo.

+0

Gracias por la respuesta extremadamente rápida. Sin embargo, cuando ejecuto con o sin depurador (F5 o Ctrl-F5) no hay diferencia. La variable tmp en la pila de hecho lo explicaría. – jeroentrappers

+0

@jeroentrappers: ¿Está construyendo en la configuración de depuración o liberación? (Estoy viendo "Finalizer llamó a zombieGuy" si eso es lo que estás esperando pero no viendo ...) –

+0

@jeroentrappers: mira mi edición en la parte superior de la publicación. –

1

Si desea objetos que no mueren, no necesita meterse con los finalizadores. Sólo tienen una lista estática privada de todas las instancias, y añadir objetos a la lista a medida que se crean:

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

    public Immortal() 
    { 
     _immortals.Add(this); 
    } 
} 
+0

Me doy cuenta de que, sin embargo, el propósito de la demostración es demostrar el finalizador. – jeroentrappers

Cuestiones relacionadas