2011-01-19 10 views
10

Hay muchas preguntas SO preguntando cómo detectar fugas de objetos identificables. Parece que la respuesta es "you can't".Detectando objetos IDsposable "filtrados"

Acabo de comprobar con el caso de prueba más trivial, que FxCop 10.0 no lo hace, ReSharper 4 con MSVS2010 no lo hace.

Esto me parece incorrecto, peor que las pérdidas de memoria en C (para lo cual al menos hemos establecido herramientas para detectar).

Estaba pensando: ¿Es posible, utilizando la reflexión y otras técnicas avanzadas poco claras, que puedo inyectar un cheque en tiempo de ejecución, en el finalizador para ver si se ha llamado al Dispose?

¿Qué hay de trucos de magia con WinDBG + SOS?

Incluso si no hay herramientas existentes para hacerlo, me gustaría saber si esto es teóricamente posible (mi C# no es muy nítida).

Ideas?

NOTA El título de esta pregunta podría haber sido engañoso. La verdadera pregunta aquí debería ser si un objeto IDisposable ha sido Disposed() correctamente. Quedarme eliminado por el GC no cuenta ya que lo considero un error.

Editar: Solución: .NET Memory Profiler hace el trabajo. Solo debemos enviar varios correos electrónicos no deseados al GC.Collect() al final del programa para permitir que nuestro generador de perfiles pueda recoger las estadísticas correctamente.

+0

La razón por la que existen herramientas para C++ pero quizás no para C# es que los recursos en C# son fundamentalmente diferentes ya que los recursos no administrados * ya no están acoplados a la duración del objeto *. Lo que se puede rastrear, tanto en C# como en C++, es la duración del objeto y si un objeto se ha eliminado correctamente. Pero los recursos desechables en C# no están de ninguna manera ligados a la duración del objeto, lo que hace que el seguimiento sea mucho más difícil. A modo de comparación, intente rastrear los recursos filtrados de GDI que no están vinculados a través de RAII para objetar el tiempo de vida en C++. No es tan fácil tampoco. –

+0

He estado reflexionando sobre esto un poco. Desarrollé el hábito de verificar rápidamente los tipos mientras escribo código para ver si heredan de 'IDisposable'. Si lo hacen, los envuelvo en 'usar' en el alcance que necesitan para vivir. No hace nada por el código existente, pero pensé que lo mencionaría. –

+0

Eche un vistazo a esta publicación donde puede usar el análisis de código de Visual Studio para detectar problemas de iDisposable en tiempo de compilación: http://stackoverflow.com/a/6213977/2862 –

Respuesta

11

No buscó lo suficiente. Hay muchos .NET Memory Profilers por ahí que verán su programa mientras se ejecuta y le permitirán saber dónde y cómo se usa su memoria (y qué se está filtrando).

Me echa un vistazo a cualquiera de los siguientes:

Microsoft's CLR Memory Profiler (free)
RedGate ANTS Memory Profiler
JetBrain's DotTrace (includes code profiler as well)
SciTech .NET Memory Profiler

actualización

.NET memoria Profiler de SciTech tiene una característica llamada 'Desechar Rastreador 'eso se ajusta a la factura de los OP's solicitud de rastrear solo las llamadas a Dispose en su aplicación.

+0

¿Detecta 'Dispose' o simplemente memory? ¿Qué pasa si mi objeto 'IDisposable' solo tiene' Console.WriteLine ("baz"); '(ejemplo pobre, lo sé, pero entiendes el punto) y quiero asegurarme de que el GC lo llame realmente NO? – kizzx2

+0

@ kizzx2 - Detectará todo, pero a partir de ahí puede reducirlo para encontrar lo que está buscando. –

+0

Tuve las mejores experiencias con ANTS Memory Profiler de RedGate. –

3

puede hacerlo, agregando un Finalizador a sus objetos IDisposable. En el finalizador, puede verificar si el objeto ha sido eliminado o no. Si no se ha eliminado, puede afirmarlo o escribir algo en un registro, o lo que sea.

~Disposable() 
{ 
#if DEBUG 
      // In debug-builds, make sure that a warning is displayed when the Disposable object hasn't been 
      // disposed by the programmer. 

      if(_disposed == false) 
      { 
       System.Diagnostics.Debug.Fail ("There is a disposable object which hasn't been disposed before the finalizer call: {0}".FormatString (this.GetType().Name)); 
      } 
#endif 
      Dispose (false); 
} 

Usted puede factorizar esta funcionalidad en una clase base - Disposable - por ejemplo, que se puede utilizar como una plantilla para implementar el patrón Disposable por ejemplo.

Así, por ejemplo:

/// <summary> 
    /// Abstract base class for Disposable types.  
    /// </summary> 
    /// <remarks>This class makes it easy to correctly implement the Disposable pattern, so if you have a class which should 
    /// be IDisposable, you can inherit from this class and implement the DisposeManagedResources and the 
    /// DisposeUnmanagedResources (if necessary). 
    /// </remarks> 
    public abstract class Disposable : IDisposable 
    { 
     private bool     _disposed = false; 

     /// <summary> 
     /// Releases the managed and unmanaged resources. 
     /// </summary> 
     public void Dispose() 
     { 
      Dispose (true); 
      GC.SuppressFinalize (this); 
     } 

     /// <summary> 
     /// Releases the unmanaged and managed resources. 
     /// </summary> 
     /// <param name="disposing">When disposing is true, the managed and unmanaged resources are 
     /// released. 
     /// When disposing is false, only the unmanaged resources are released.</param> 
     [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")] 
     protected void Dispose(bool disposing) 
     { 
      // We can suppress the CA1063 Message on this method, since we do not want that this method is 
      // virtual. 
      // Users of this class should override DisposeManagedResources and DisposeUnmanagedResources. 
      // By doing so, the Disposable pattern is also implemented correctly. 

      if(_disposed == false) 
      { 
       if(disposing) 
       { 
        DisposeManagedResources(); 
       } 
       DisposeUnmanagedResources(); 

       _disposed = true; 
      } 
     } 

     /// <summary> 
     /// Override this method and implement functionality to dispose the 
     /// managed resources. 
     /// </summary> 
     protected abstract void DisposeManagedResources(); 

     /// <summary> 
     /// Override this method if you have to dispose Unmanaged resources. 
     /// </summary> 
     protected virtual void DisposeUnmanagedResources() 
     { 
     } 

     /// <summary> 
     /// Releases unmanaged resources and performs other cleanup operations before the 
     /// <see cref="Disposable"/> is reclaimed by garbage collection. 
     /// </summary> 
     [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")] 
     ~Disposable() 
     { 
#if DEBUG 
      // In debug-builds, make sure that a warning is displayed when the Disposable object hasn't been 
      // disposed by the programmer. 

      if(_disposed == false) 
      { 
       System.Diagnostics.Debug.Fail ("There is a disposable object which hasn't been disposed before the finalizer call: {0}".FormatString (this.GetType().Name)); 
      } 
#endif 
      Dispose (false); 
     } 
    } 
+0

Eso es lo que Raymond en Old New Thing [sugerido] (http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049634.aspx). Lo uso todo el tiempo para mis propias clases, ahora estaba pensando si también podría atrapar, p. 'FileStream'. – kizzx2

+0

este es un enfoque deficiente, ya que no se puede mantener y agrega mucho desorden y complejidad. Use un generador de perfiles en su lugar. Uso Red Gate Memory Profiler todo el tiempo. –

+0

¿Por qué es un enfoque pobre? ¿Por qué no es mantenible y le agrega complejidad? Si factorizas este código en una clase base, que se llama 'Desechable' por ejemplo, no veo ningún problema. Junto a eso, puede usar esta clase base como una 'plantilla' para implementar correctamente el 'Patrón disponible'. –

1

Si bien la recomendación de @Justin Niessner funciona, me parece que el uso de un generador de perfiles completo soplado demasiado pesado.

Creé mi solución de cocimiento casero: EyeDisposable. Instrumentos ensamblados para detectar cuando Dispose no han sido llamados.

+0

¡Gracias, se ve muy bien! Sin embargo, me hubiera encantado el análisis * estático * (quizás como un complemento R #). Un pequeño comentario, en la sección * Cloning * del archivo léame, debe ejecutar 'git submodule init' antes de' git submodule update'. –

+1

@OhadSchneider Gracias por el aviso - el análisis estático sería genial, pero tiene muchos falsos positivos para casos no triviales - y honestamente es demasiado más complicado que el alcance original de esta _small_ utilidad: P – kizzx2