¿Cómo puedo escribir una prueba unitaria para determinar si un objeto puede ser recolectado como basura?

En relación con mi pregunta anterior , necesito verificar si un componente que creará una instancia de Castle Windsor, puede ser basura recolectada después de que mi código haya terminado de usarlo. He intentado con la sugerencia en las respuestas de la pregunta anterior, pero parece que no funciona como esperaba, al menos para mi código. Así que me gustaría escribir una prueba unitaria que pruebe si una instancia específica del objeto puede ser recolectada después de que se haya ejecutado parte de mi código.

¿Es posible hacerlo de manera confiable?

EDITAR

Actualmente tengo la siguiente prueba basada en la respuesta de Paul Stovell, que tiene éxito:

[TestMethod] public void ReleaseTest() { WindsorContainer container = new WindsorContainer(); container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy(); container.AddComponentWithLifestyle(LifestyleType.Transient); Assert.AreEqual(0, ReleaseTester.refCount); var weakRef = new WeakReference(container.Resolve()); Assert.AreEqual(1, ReleaseTester.refCount); GC.Collect(); GC.WaitForPendingFinalizers(); Assert.AreEqual(0, ReleaseTester.refCount, "Component not released"); } private class ReleaseTester { public static int refCount = 0; public ReleaseTester() { refCount++; } ~ReleaseTester() { refCount--; } } 

¿Estoy bien asumiendo que, de acuerdo con la prueba anterior, puedo concluir que Windsor no perderá memoria cuando use la NoTrackingReleasePolicy?

Esto es lo que hago normalmente:

 [Test] public void MyTest() { WeakReference reference; new Action(() => { var service = new Service(); // Do things with service that might cause a memory leak... reference = new WeakReference(service, true); })(); // Service should have gone out of scope about now, // so the garbage collector can clean it up GC.Collect(); GC.WaitForPendingFinalizers(); Assert.IsNull(reference.Target); } 

NB: En muy pocas ocasiones debe llamar a GC.Collect () en una aplicación de producción. Pero la prueba de fugas es un ejemplo de dónde es apropiado.

Quizás podría tener una WeakReference y luego verificar que ya no esté viva (es decir,! IsAlive) después de que las pruebas se hayan completado.

Basado en la respuesta de Paul , creé un método Assert más reutilizable. Como las string se copian por valor, agregué una verificación explícita para ellas. Pueden ser recolectados por el recolector de basura.

 public static void IsGarbageCollected( ref TObject @object ) where TObject : class { Action emptyAction = o => { }; IsGarbageCollected( ref @object, emptyAction ); } public static void IsGarbageCollected( ref TObject @object, Action useObject ) where TObject : class { if ( typeof( TObject ) == typeof( string ) ) { // Strings are copied by value, and don't leak anyhow. return; } int generation = GC.GetGeneration( @object ); useObject( @object ); WeakReference reference = new WeakReference( @object, true ); @object = null; // The object should have gone out of scope about now, // so the garbage collector can clean it up. GC.Collect( generation, GCCollectionMode.Forced ); GC.WaitForPendingFinalizers(); Assert.IsNull( reference.Target ); } 

Las siguientes pruebas unitarias muestran que la función está funcionando en algunos escenarios comunes.

 [TestMethod] public void IsGarbageCollectedTest() { // Empty object without any references which are held. object empty = new object(); AssertHelper.IsGarbageCollected( ref empty ); // Strings are copied by value, but are collectable! string @string = ""; AssertHelper.IsGarbageCollected( ref @string ); // Keep reference around. object hookedEvent = new object(); #pragma warning disable 168 object referenceCopy = hookedEvent; #pragma warning restre 168 AssertHelper.ThrowsException( () => AssertHelper.IsGarbageCollected( ref hookedEvent ) ); GC.KeepAlive( referenceCopy ); // Still attached as event. Publisher publisher = new Publisher(); Subscriber subscriber = new Subscriber( publisher ); AssertHelper.ThrowsException( () => AssertHelper.IsGarbageCollected( ref subscriber ) ); GC.KeepAlive( publisher ); } 

Debido a las diferencias al usar la configuración de Release (supongo que las optimizaciones del comstackdor), algunas de estas pruebas unitarias fallarían si no se GC.KeepAlive() .

El código fuente completo (incluidos algunos de los métodos de ayuda utilizados) se puede encontrar en mi biblioteca .

Use el marco de la unidad dotMemory (es gratis)

 [TestMethod] public void ReleaseTest() { // arrange WindsorContainer container = new WindsorContainer(); container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy(); container.AddComponentWithLifestyle(LifestyleType.Transient); var target = container.Resolve() // act target = null; // assert dotMemory.Check(memory => Assert.AreEqual( 0, memory.GetObjects(where => where.Type.Is().ObjectsCount, "Component not released"); } 

Esta no es una respuesta, sin embargo, es posible que desee intentar ejecutar su código en los modos Depurar y Liberar (para comparar bien).

En mi experiencia, la versión de depuración del código JIT es más fácil de depurar y, por lo tanto, las referencias pueden permanecer activas más tiempo (creo scope de la función). Sin embargo, el código JITed en modo Release puede tener los objetos listos para la recostackción rápidamente una vez que está fuera scope y si ocurre una Colección.

También no respondiendo tu pregunta: 🙂
Me interesaría ver que depure este código usando Visual Studio en el modo Interop (Administrado y Nativo) y luego se rompe después de mostrar un cuadro de mensaje o algo así. Luego puede abrir Depurar-> Windows-Inmediato y luego escribir

 load sos (Change to thread 0) !dso !do  !gcroot  (and look for any roots) 

(o puede usar Windbg como otros publicaron en publicaciones anteriores)

Gracias, Aaron