Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I tell when my `IDisposable` types have not explicitly been disposed?

It's hard to find at design/compile time types that are IDisposable but that are not disposed correctly. What methods are there at runtime to find them?

like image 378
Steve Dunn Avatar asked Feb 27 '23 19:02

Steve Dunn


2 Answers

It is good practice to implement IDisposable if your type is resource hungry, but, this is just good practice and cannot be enforced by the compiler.

One thing you can do to make abusing IDisposables more noticeable, is to make them throw or assert in the finalizer (remember, if your type is disposed properly, the finalizer won't get called because you'll have called GC.SuppressFinalize in your dispose method). The following program shows an error in the debugger's output window when the app finishes because Hog is not correctly disposed.

    class Program
    {
        static void Main(string[] args)
        {
            new Hog( ) ;
        }
    }

    class Hog : IDisposable
    {          
        public void Dispose( )
        {
            Dispose( true ) ;
            GC.SuppressFinalize( this ) ;
        }

        protected virtual void Dispose( bool disposing )
        {
            GC.SuppressFinalize( this );
        }

        ~Hog( )
        {
            Debug.Fail( @"You didn't dispose me!" ) ;
            Dispose( false ) ;
        }
    }

You'll see the following error in the debugger:

---- DEBUG ASSERTION FAILED ----
---- Assert Short Message ----
You didn't dispose me!
---- Assert Long Message ----
    at Hog.Finalize()

but, if you did use the disposable object correctly, such as:

static void Main(string[] args)
{
    using (new Hog())
        ;
}

...you won't see anything.

To make things even more useful, you can record the current stack trace in the constructor and dump it out in the destructor. So the new, more useful Hog would look like:

    class Hog : IDisposable
    {
        readonly StackTrace _stackTrace ;

        public Hog( )
        {
#if DEBUG
            _stackTrace = new StackTrace();
#endif
        }

        public void Dispose( )
        {
            Dispose( true ) ;
            GC.SuppressFinalize( this ) ;
        }

        protected virtual void Dispose( bool disposing )
        {
            GC.SuppressFinalize( this );
        }

        ~Hog( )
        {
#if DEBUG
            Debug.WriteLine("FinalizableObject was not disposed" + _stackTrace.ToString());
#endif
            Dispose( false ) ;
        }
    }

and using it (without disposing it) would give you this in the debugger output window:

FinalizableObject was not disposed   at ConsoleApplication1.Hog..ctor()
   at ConsoleApplication1.Program.Main(String[] args)
   at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
like image 108
Steve Dunn Avatar answered Mar 01 '23 15:03

Steve Dunn


Code analysis tools like FxCop (included in Team Editions of Visual Studio or as a free download) can detect this.

Though there will be occasional false negatives/positives.

like image 41
Joe Avatar answered Mar 01 '23 15:03

Joe