2011-11-30 8 views
8

Tengo una clase que se aferra a un delegado, con el fin de evaluar perezosamente algo más tarde.GC de delegados, ¿qué me estoy perdiendo? (mi delegado no se recoge)

Una vez que lo he evaluado, llamando al delegado, borro la referencia al delegado, esperando que sea elegible para la recopilación. Después de todo, podría aferrarse a un mundo de variables locales si se construyera como un método anónimo.

Traté de construir una prueba unitaria para verificar esto, pero parece que no funciona de la manera que lo planeé, en cambio parece que mis suposiciones sobre WeakReference (que utilicé para fines de prueba aquí), o alguna otra suposición, no tiene agua.

Tome un vistazo a este código, que se puede ejecutar en LINQPad

void Main() 
{ 
    WeakReference wr; 
    Lazy<int> l; 
    CreateTestData(out wr, out l); 

    wr.IsAlive.Dump();     // should be alive here 

    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
    GC.Collect(); 

    wr.IsAlive.Dump();     // and alive here as well 
    l.Value.Dump();      // but now we clear the reference 

    GC.Collect();      // so one of these should collect it 
    GC.WaitForPendingFinalizers(); 
    GC.Collect(); 

    wr.IsAlive.Dump();     // and then it should be gone here 
    GC.KeepAlive(l); 
} 

void CreateTestData(out WeakReference wr, out Lazy<int> l) 
{ 
    Func<int> f =() => 10; 
    wr = new WeakReference(f); 
    l = new Lazy<int>(f); 
} 

public class Lazy<T> 
{ 
    private Func<T> _GetValue; 
    private T _Value; 

    public Lazy(Func<T> getValue) 
    { 
     _GetValue = getValue; 
    } 

    public T Value 
    { 
     get 
     { 
      if (_GetValue != null) 
      { 
       _Value = _GetValue(); 
       _GetValue = null; 
      } 
      return _Value; 
     } 
    } 
} 

estaba asumiendo que:

  1. Independientemente de versión de depuración, depurador asociado, desde que cree el delegado en una método separado, del cual vuelvo, no debería haber nada aferrado al delegado, excepto el WeakReference y el Lazy<T> objetos
  2. Si le pregunto al Lazy<T> objeto a renunciar a su referencia al delegado, lo que reduciría las referencias a sólo el que WeakReference se aferra a
  3. Y luego forzar una recolección completa, suponiendo que si la única izquierda referencia es el que está en WeakReference
  4. a continuación, el delegado
  5. serían recogidos, y mi WeakReference indicaría que el objeto ya no está vivo

la salida del código fue por lo tanto espera que sea (con comentarios):

true // not gc'ed after construction 
true // not gc'ed after full GC, still beind held by Lazy<T> 
10 // value from calling delegate 
false // but is now gc'ed, Lazy<T> no longer has a reference to it 

Pero en lugar de la salida es:

true 
true 
10 
true 

¿Alguien puede arrojar algo de luz sobre lo que me falta aquí?

Respuesta

6

El "problema" es que el compilador se da cuenta de que puede reutilizar una sola instancia de delegado para siempre. No captura ningún contexto, ni siquiera la referencia implícita this. Así que esto:

void CreateTestData(out WeakReference wr, out Lazy<int> l) 
{ 
    Func<int> f =() => 10; 
    ... 
} 

se convierte en algo así como:

static Func<int> hiddenDelegate; 

static int HiddenMethod() 
{ 
    return 10; 
} 

void CreateTestData(out WeakReference wr, out Lazy<int> l) 
{ 
    if (hiddenDelegate == null) 
    { 
     hiddenDelegate = HiddenMethod; 
    } 

    Func<int> f = hiddenDelegate; 
    ... 
} 

mirar el código en ildasm (o reflector sin optimización encendido) para ver exactamente lo que está pasando.

+0

Ok, eso tiene sentido. Cambié el delegado para que usara una variable local para calcular sus resultados, y eso fue efectivamente recopilado. –

+0

Pregunta lateral: supongo que el compilador no es lo suficientemente inteligente como para darse cuenta de que múltiples delegados, del mismo tipo, que devuelven el mismo valor constante, pueden crearse una vez y reutilizarse en todas partes. –

+0

Básicamente, mi objetivo era realizar una prueba que verifique que la referencia al delegado se abandona en el momento adecuado, para que sea posible para la recolección de basura donde esté permitido, no necesariamente que ese delegado en particular fue recolectado.Cambiar al delegado solucionó ligeramente ese problema, y ​​ahora las pruebas se ejecutan como se esperaba. –

Cuestiones relacionadas