2011-01-19 9 views
10

Ci sono molte domande, quindi, che si chiede come rilevare la perdita di oggetti IDisposable. Sembra che la risposta sia "you can't".Rilevamento di oggetti identificabili "fuoriusciti"

Ho appena controllato con il caso di test più banale, che FxCop 10.0 non lo fa, ReSharper 4 con MSVS2010 non lo fa.

Questo mi sembra sbagliato, peggio di perdite di memoria in C (per il quale almeno abbiamo stabilito strumenti per rilevare).

Stavo pensando: è possibile, utilizzando la riflessione e altre tecniche avanzate oscure, che posso iniettare un controllo in fase di esecuzione, nel finalizzatore per vedere se è stato chiamato Dispose?

E i trucchi magici con WinDBG + SOS?

Anche se non ci sono strumenti esistenti per farlo, mi piacerebbe sapere se questo è teoricamente possibile (il mio C# non è molto nitido).

Idee?

NOTA Il titolo di questa domanda potrebbe essere fuorviante. La vera domanda qui dovrebbe essere se un oggetto IDisposable è stato Disposed() correttamente. Ottenere smaltito dal GC non conta perché lo considero un errore.

Modifica: Soluzione: .NET Memory Profiler fa il lavoro. Abbiamo solo bisogno di inviare spam diversi GC.Collect() alla fine del programma per consentire al nostro profiler di raccogliere correttamente le statistiche.

+0

Il motivo per cui esistono strumenti per C++ ma forse non per C# è che le risorse in C# sono fondamentalmente diverse poiché le risorse non gestite sono * non più abbinate alla durata dell'oggetto *. Ciò che può essere tracciato, sia in C# che in C++, è la durata dell'oggetto e se un oggetto è stato correttamente eliminato. Ma le risorse usa e getta in C# non sono in alcun modo vincolate alla durata dell'oggetto, il che rende molto più difficile rintracciarle. Per fare un confronto, prova a tenere traccia delle risorse GDI trapelate che non sono collegate tramite RAII alla durata dell'oggetto in C++. Neanche così facile. –

+0

Ho riflettuto un po 'su questo. Ho sviluppato l'abitudine di controllare rapidamente i tipi mentre scrivo codice per vedere se ereditano da 'IDisposable'. Se lo fanno, li avvolgo in "uso" dello scopo di cui hanno bisogno per vivere. Non fa nulla per il codice esistente ma ho solo pensato di parlarne. –

+0

Dai un'occhiata a questo post in cui puoi utilizzare l'analisi del codice di Visual Studio per rilevare problemi di iDisposable in fase di compilazione: http://stackoverflow.com/a/6213977/2862 –

risposta

11

Non hai cercato abbastanza. Ci sono molti Profiler di memoria .NET là fuori che guarderanno il tuo programma mentre è in esecuzione e ti faranno sapere dove/come viene utilizzata la tua memoria (e cosa sta perdendo).

avrei fatto il check out uno dei seguenti:

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

Aggiornamento

di SciTech NET Memory Profiler ha una funzione chiamata 'Smaltire Tracker 'che si adatta al conto per gli OP richiesta di tenere traccia solo delle chiamate Dispose nella loro applicazione.

+0

Rileva 'Dispose' o solo memoria? Che cosa succede se il mio oggetto 'IDisposable' ha solo' Console.WriteLine ("baz"); '(esempio povero, lo so, ma ottieni il punto) e voglio assicurarmi che in realtà sia chiamato NOT dal GC? – kizzx2

+0

@ kizzx2 - Rileverà tutto, ma da lì puoi restringerlo per trovare quello che stai cercando. –

+0

Ho avuto le migliori esperienze con ANTS Memory Profiler di RedGate. –

3

puoi farlo aggiungendo un Finalizzatore ai tuoi oggetti IDisposable. Nel finalizzatore, è possibile verificare se l'oggetto è stato eliminato o meno. Se non è stato eliminato, puoi dichiararlo o scrivere qualcosa su un registro o altro.

~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); 
} 

È possibile fattore questa funzionalità in una classe di base - Disposable - per esempio, che può essere utilizzato come modello per implementare il modello Disposable per esempio.

In questo modo, per esempio:

/// <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

Ecco cosa Raymond at the Old New Thing [suggerito] (http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049634.aspx). Lo uso sempre per le mie lezioni, ora pensavo se potessi anche cogliere, ad es. 'FileStream'. – kizzx2

+0

questo è un approccio povero in quanto non è mantenibile e aggiunge troppa confusione e complessità. Usa invece un profiler. Io uso sempre il Red Gate Memory Profiler. –

+0

Perché è un approccio povero? Perché non è maneggevole e aggiunge complessità? Se si calcola questo codice in una classe base, che per esempio si chiama 'Disposable', non vedo alcun problema. Accanto a questo, puoi usare questa classe base come un 'modello' per implementare correttamente il 'Modello usa e getta'. –

1

Mentre la raccomandazione di @Justin Niessner funziona, trovo che l'utilizzo di un profiler in piena regola troppo pesante.

Ho creato la mia soluzione di home-brew: EyeDisposable. Esso assembli gli strumenti per rilevare quando non sono stati chiamati Dispose.

+0

Grazie, sembra molto bello! Avrei amato l'analisi * statica * per questo però (forse come un plugin R #). Un piccolo commento, nella sezione * Cloning * del readme, deve eseguire 'git submodule init' prima di' git submodule update'. –

+1

@OhadSchneider Grazie per il controllo - l'analisi statica sarebbe interessante ma destinata ad avere molti falsi positivi per casi non banali - ed è onestamente molto più complicata dello scopo originale di questa utility _small_: P – kizzx2

Problemi correlati