2012-07-13 10 views
11

Supongamos que tengocaso especial

void foo() { 
    Bar bar = new Bar(); // bar is never referred to after this line 
    // (1) 
    doSomethingWithoutBar(); 
} 

A (1), es el objeto bar está apuntando a elegibles para la recolección de basura? ¿O bar también debe quedar fuera del alcance? ¿Hace una diferencia si GC.Collect es llamado por doSomethingWithoutBar?

Esto es relevante para saber si Bar tiene un destructor (C#) o algo así de funky.

+2

Voy a esperar a que Eric Lippert pase por aquí y se ocupe de esto ... –

+0

@StefanH, creo que he visto una respuesta de Eric sobre una pregunta muy similar a esta (o una entrada de blog de él) pero mi google-fu está fallando en este momento = ( – Rob

+0

@Rob - Lo sé! Intenté simplemente buscar en Google "C# Garbage Collection" porque supuse que sería el primer resultado. –

Respuesta

8

Los objetos pueden ser elegibles para la recolección de basura tan pronto como sea seguro que ya no se utilizarán. Es completamente posible que bar sea basura recolectada antes de que la variable salga del alcance.

Prueba:

using System; 

class Bar 
{ 
    ~Bar() { Console.WriteLine("Finalized!"); } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Bar bar = new Bar(); 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     Console.WriteLine("Press any key to exit..."); 
     Console.ReadLine(); 
    } 
} 

Ejecutar en modo de disparo (debido a que no quede recogido en el modo de depuración).

Salida:

 
Finalized! 
Press any key to exit... 

También trabaja en ideone que utiliza Mono. El resultado es el mismo.

+0

Creo que esperará a que se complete el alcance para garantizar que no haya ninguna referencia de * runtime * a ese objeto local. – Tigran

+0

@Tigran, como es una variable local, ¿cómo podría haber alguna referencia en * runtime * para ello? =) – Rob

+0

No pude reproducir esta situación. En cualquier versión del marco: 2, 3.0, 3.5, 4.0. – Hailton

1

Sin definir a qué versión del CLR se refiere, es imposible difícil de ser definitivo sobre el comportamiento que verá aquí.

A hipotético CLR podría, en este ejemplo, suponiendo que la siguiente es cierto:

  1. El constructor de Bar no hace nada
  2. No hay campos que se inicializan (es decir, no hay potencial efectos secundarios a la construcción de objetos)

Descarte completamente la línea Bar bar = new Bar(); y optimícela ya que "no hace nada".

En lo que respecta a mi memoria, en las versiones actuales de la CLR bar es elegible para la recolección de basura en el momento después de haberla construido.

+0

Creo que se refiere a un "compilador hipotético" –

+0

@ StefanH, si por "compilador hipotético" te refieres a "hipotético ** compilador JIT **", entonces sí, eso es lo que quiero decir =) De lo contrario 'Bar' podría definirse en un ensamblaje diferente que podría ser intercambiado post-'csc' compilar, lo que hace que 'Bar' en el tiempo de compilación se elimine, incluso si' Bar' en tiempo de ejecución hubiera hecho algo ... (piense en el desarrollo de complementos como un escenario posible) – Rob

+0

Estaba pensando más en que sería eliminado antes se crea el CLR, que está en tiempo de compilación, ¿verdad? EDITAR: No, estoy equivocado (CLR no significa lo que pensé que significaba), estaba pensando que podría ser eliminado antes de que se genere el CIL. –

3

A partir de una lectura rápida de la especificación, parece que es específica de la implementación. Es permitido a la basura lo recoge, pero no requiere a.

que obtener esta información de una nota en la sección 10.9 "Gestión automática de la memoria" de la ECMA Spec:

[Nota: Las implementaciones podrían elegir para analizar código para determinar la que las referencias a un objeto puede utilizarse en el futuro.Para la instancia , si una variable local que está en el alcance es la única referencia existente a un objeto, pero esa variable local nunca se refiere a en cualquier posible continuación de ejecución desde el punto de ejecución actual en el procedimiento una implementación podría (pero no es necesario ) tratar el objeto como si ya no estuviera en uso. nota final]

Énfasis mío.

1

Marc responde a la pregunta, pero aquí está la solución:

void foo() { 
    Bar bar = new Bar(); // bar is never referred to after this line 
    // (1) 
    doSomethingWithoutBar(); 

    GC.KeepAlive(bar); // At the point where you no longer need it 
} 
1

Esto sin duda puede ocurrir. Por ejemplo, aquí es una demostración de que una instancia puede ser finalizado mientras se está ejecutando su constructor:

class Program 
{ 
    private static int _lifeState; 
    private static bool _end; 

    private sealed class Schrodinger 
    { 
     private int _x; 

     public Schrodinger() 
     { 
      //Here I'm using 'this' 
      _x = 1; 

      //But now I no longer reference 'this' 
      _lifeState = 1; 

      //Keep busy to provide an opportunity for GC to collect me 
      for (int i=0;i<10000; i++) 
      { 
       var garbage = new char[20000]; 
      } 

      //Did I die before I finished being constructed? 
      if (Interlocked.CompareExchange(ref _lifeState, 0, 1) == 2) 
      { 
       Console.WriteLine("Am I dead or alive?"); 
       _end = true; 
      } 
     } 

     ~Schrodinger() 
     { 
      _lifeState = 2; 
     } 
    } 

    static void Main(string[] args) 
    { 
     //Keep the GC churning away at finalization to demonstrate the case 
     Task.Factory.StartNew(() => 
      { 
       while (!_end) 
       { 
        GC.Collect(); 
        GC.WaitForPendingFinalizers(); 
       } 
      }); 

     //Keep constructing cats until we find the desired case 
     int catCount = 0; 
     while (!_end) 
     { 
      catCount++; 
      var cat = new Schrodinger(); 
      while (_lifeState != 2) 
      { 
       Thread.Yield(); 
      } 
     } 
     Console.WriteLine("{0} cats died in the making of this boundary case", catCount); 
     Console.ReadKey(); 
    } 
} 

Para que esto funcione, es necesario emitir una versión de lanzamiento y ejecutar fuera de Visual Studio (como se de lo contrario, el depurador inserta un código que previene el efecto). He probado esto con VS 2010 que apunta a .NET 4.0 x64.

Puede ajustar las iteraciones en el ciclo 'keep busy' (mantener ocupado) para impactar la probabilidad de que el final del Cat finalice antes de su construcción completa.

Cuestiones relacionadas