Making sure objects are properly disposed could be tricky. Here are some ways I deal with this.

The problem

When you are creating an object that implements IDisposable, you are supposed to dispose this object when you are done with it. This way the object has a chance to close files, close network connections. If you do not do this these resources will be in use for an unknown amount of time and this could cause unexpected errors.

Normally you do this with the using keyword:

Using sample
using(var myMemoryStream = new MemoryStream())
{
    // more code
}

Doing like this myMemoryStream will be disposed when it is no longer in use. In principle this is easy. But it is easy to simply forget about this. And there are more complicated scenarios were this is not possible.

The solution

One solution I have been using for a long time only works for classes you implement yourself and are using IDisposable. But it is neat anyway.

Look on this sample code:

Using sample
using System;
using System.Threading;

namespace MissingDisposeCatcher
{
    class MyDisposable : IDisposable
    {
        private bool disposedValue;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: dispose managed state (managed objects)
                }

                // TODO: free unmanaged resources (unmanaged objects) and override finalizer
                // TODO: set large fields to null
                disposedValue = true;
            }
        }

        ~MyDisposable()
        {
            // This object was not disposed properly! Break execution in debugger.
            if (System.Diagnostics.Debugger.IsAttached)
            {
                System.Diagnostics.Debugger.Break();
            }

            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
            Dispose(disposing: false);
        }

        public void Dispose()
        {
            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }

    class Program
    {
        static void Main()
        {
            while(true)
            {
                _ = new MyDisposable();

                GC.Collect();

                Thread.Sleep(10);
            }
        }
    }
}

Here MyDisposable implements the normal dispose pattern. But I have done a minor tweak, I have added this destructor code:

Using sample
~MyDisposable()
{
    // This object was not disposed properly! Break execution in debugger.
    if (System.Diagnostics.Debugger.IsAttached)
    {
        System.Diagnostics.Debugger.Break();
    }

    // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
    Dispose(disposing: false);
}

This is the destructor/finalizer of MyDisposable. When the object is properly disposed, GC.SuppressFinalize(this) is called and this will make sure that the finalizer is never executed. If the object is not disposed, then the destructor is executed. This will first check if the debugger is attached, and if it is it will call System.Diagnostics.Debugger.Break(); which will behave exactly like a breakpoint. Then debugger will stop and now you know something has went wrong.

Detecting the issue during compiling

Another solution I have started to use recently is to use the code quality analyzers that are enabled by default in .NET 5. But the specific analyzer CA2000: Dispose objects before losing scope is disabled by default. To change it, do this:

  • Go a project in Visual Studio.
  • Select Dependencies.
  • Select Analyzers.
  • Select Microsoft.CodeAnalysis.NetAnalyzers.
  • Right click on the rule CA2000 and then set the Severity to Warning.
  • The setting is now saved in .editorconfig.
    • If you do not have an .editorconfig before, this file is now created on the project level. You are then being suggested to move it to the solution level, which is probably a good choice.

An editorconfig file with this rule enabled looks like this:

.editorconfig
[*.cs]

# CA2000: Dispose objects before losing scope
dotnet_diagnostic.CA2000.severity = warning

It is worth mention that CA2000 only covers one scenario, in my experience the most common one. There are more analyzers that covers more scenarios with dispose:

Be aware that analyzers will make it slower to compile your code. It is a not a major issue but something to be aware about. Also notice that the analyzers do not work on .NET Standard libraries, at least not by default.

Summary

Disposing objects properly are important so I use both solutions mentioned above.