Everything That Every .NET Developer Needs to Know About Disposable Types: Properly Implementing the IDisposable Interface

I part 1 of this series of this article, I talked about how it is very critical for every developer to know exactly how memory management works in the .NET runtime. If you don’t, you will cause performance issues but more seriously it can cause virtual memory leaks. Part 1 showed how to properly dispose of types that implement IDisposable. In this article, I will show the proper way to implement the IDisposable interface in types that you create that contain disposable fields. In addition, I will show how to implement the dispose pattern in types that inherit a type that implements IDisposable.  

The Problem

Here is an example of a typical class that I will come by when analyzing code.

1   public class PdfStreamer
2   {
3       private readonly MemoryStream _stream = new MemoryStream();
4  
5       public PdfStreamer()
6       {}
7   }

What needs to be dealt with in code is line #3 where _stream is defined. The MemoryStream type, along with other types that end in the name ‘Stream’ in .NET are disposable and must be disposed of when the class is collected (removed from memory) by the Garbage Collector (GC). As shown, _stream will not be collected and most likely cause a virtual memory leak.

Virtual Memory Leaks Must Be Prevented in Your Application! I Cannot Stress This Enough!

The Solution

To prevent virtual memory leaks, the IDisposable interface must be used. This is the definition of IDisposable.

public interface IDisposable

{
   void Dispose();
}

This interface requires a method called Dispose() to be added to your type, but there is more needed to properly handle these types. Here is an example of how most developers implement IDisposable.

1   public void Dispose()
2   {
3       _stream.Dispose();
4   }

While this satisfies the interface as shown in line #3, this is not the proper pattern since does not help the GC destroy these objects properly. It relies on every developer to call Dispose() on the variable when it’s no longer in use. After 20 years of coding in .NET, this never happens 100% of the time. We need to do a few things to properly implement this pattern.

First

First, we must create a new Dispose() method and include a Boolean disposing parameter. Here is how I did it for this class.

1   private bool _disposed;
2  
3   protected virtual void Dispose(bool disposing)
4   {
5     if (this._disposed)
6     {
7       return;
8     }
9  
10   if (disposing)
11   {
12     this._stream?.Dispose();
13   }
14
15   this._disposed = true;
16 }

If your type is sealed, then this method would look like this:

3   private void Dispose(bool disposing)
4   {
5     if (this._disposed)
6     {
7       return;
8     }
9  
10   if (disposing)
11   {
12     this._stream?.Dispose();
13   }
14
15   this._disposed = true;
16 }

As you can see in this example, the method exits if the object has already been disposed of. Trying to dispose of an object twice or more can lead to undesirable outcomes. Then, if disposing is true, Dispose() is called on the _stream field as shown in line #12. This will release anything that the MemoryStream is holding on to. Then, when the GC does its next garbage collection, the object will be removed from memory, properly.

Second

Second, we need to modify the Dispose() method as shown below:

17 public void Dispose()

18 {
19     Dispose(true);
20     GC.SuppressFinalize(this);
21 }

This is very important because of the call to GC.SuppressFinalize() on line #20. Since the Dispose() method is called on line #19, this tells the GC to ignore the finalizer that we will add next. This will speed up the destruction of the object. But we aren’t done yet, since this still relies on the developer calling Dispose().

Third

Lastly, we must add a finalizer to the class as shown below.

22 ~PdfStreamer() => Dispose();

This ensures that Dispose() is called by the finalizer if the calling code did not call Dispose() (the developer forgot to code it). This will be ignored if the Dispose() method was called by the calling code since it calls GC.SuppressFinalize().

The Full Example

What is described above is the proper way to implement the IDisposable pattern. Here is the full example.

public class PdfStreamer: IDisposable
{
  private bool _disposed;
    private readonly MemoryStream _stream = new MemoryStream();

   public PdfStreamer()
   {}

    ~PdfStreamer() => Dispose();

   protected virtual void Dispose(bool disposing)
   {
       if (this._disposed)
       {
           return;
        }

       if (disposing)
       {
           this._stream?.Dispose();
       }

       this._disposed = true;
    }

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

This example should be the only way you implement IDisposable in all your types. As I stated in part 1, when the end of the using code block is reached, Dispose() will be called automatically.

Implementing IDisposable on Types that Inherit from a Disposable Type

Next, I will describe what to do if you create a type that inherits from an IDisposable type as shown below.

public sealed class OrderPdfStreamer: PdfStreamer
{
}

For this example, there is nothing OrderPdfStreamer needs to implement since it inherits from PdfStreamer that properly implements IDisposable, but if the type also has fields that are IDisposable types, then there is more work to be done. Here is an example of the issue:

public sealed class OrderPdfStreamer: PdfStreamer
{
  private readonly MemoryStream _stream = new MemoryStream();
}

As you can guess, we also need to implement IDisposable. Here is how to do that.

1   public sealed class OrderPdfStreamer: PdfStreamer, IDisposable
2   {
3       private bool _disposed;
4       private readonly MemoryStream _stream = new MemoryStream();
5  
6       ~OrderPdfStreamer() => Dispose();
7  
8       private new void Dispose(bool disposing)
9       {
10         if (this._disposed)
11         {
12             return;
13         }
14
15         if (disposing)
16         {
17             this._stream?.Dispose();
18             base.Dispose();
19         }
20
21         this._disposed = true;
22     }
23
24     public new void Dispose()
25     {
26         Dispose(true);
27         GC.SuppressFinalize(this);
28     }
29 }

The most important part of this implementation is the call to base.Dispose() on line #18 so the base class can also clean up its disposable objects.

IDisposable Template

Below this the template I use to make it faster for me to implement IDisposable. This template is handy when I analyze code and I need to quickly fix the dispose issues.

private bool _disposed;

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

protected virtual void Dispose(bool disposing)
{
   if (this._disposed)
   {
       return;
    }

   if (disposing)
   {
       this._sometype?.Dispose();
    }

   this._disposed = true;
}

~MyClass() => Dispose();

I have also put this template online to make it easier for you to link to: https://bit.ly/IDisposableTemplate

Disposing Objects Easily with Spargine

A while ago I wrote extension methods in my open-source assemblies to make calling Dispose() easier. You can find it on NuGet.org:  https://www.nuget.org/profiles/davidmccarter

If you are using .NET 5 or 6, use the dotNetTips.Spargine.5.Core package. If you are using .NET 4.6.1 or .NET Core, use the dotNetTips.Utility.Standard.Extensions package.

DISPOSING FIELDS

I created a method called DisposeFields(). Here it is in action:

1   protected virtual void Dispose(bool disposing)
2   {
3       if (this._disposed)
4       {
5           return;
6       }
7  
8       if (disposing)
9  
10     {
11        this.DisposeFields();
12     }
13
14     this._disposed = true;
15 }

DisposeFields() on line #11 inspects all the fields in the object to see if they implement IDisposable. If any are found, then Dispose() is called. The cool thing about this method is that it prevents another developer from adding a new IDisposable field and does not add it to the Dispose(disposing) method. I call this “Future Proofing”.

DISPOSING COLLECTIONS

I’ve been using these two extension methods for a long time. But recently, I found that a client of mine is putting disposable objects into a collection. So, I wrote a method for that too called DisposeCollection() that works with IEnumerable, IEnumerable<T> and IDictionary<TKey, TValue>. DisposeCollection() will iterate the collection looking for disposable objects and then it calls Dispose() on it. DisposeFields() will check to see if the object supports IEnumerable, if it does, then it also calls DisposeCollection().

I’m not aware of any tool or analyzer that will find this issue. My recommendation is to never put disposable objects in a collection.

DISPOSING LOCAL VARIABLES

To dispose of a variable, I created the TryDispose() extension method. Here is an example on how to use it:

disposableObject.TryDispose();

TryDispose() checks to make sure the object isn’t null and then calls Dispose(). If the code is using a “using” block, then it already calls Dispose() for you so you would not need to use this extension method.

Summary

In this article, I have shown how to properly implement the Disposable pattern in the types that you create. As you can see, it can be tricky depending on how the class is designed. I challenge everyone reading this article to do a critical scan of your codebase NOW to ensure that all your IDisposable types implement the pattern correctly.

I would like to hear how many of these issues you found. Just comment below. Part 3 of this article will discuss how to use tools to help you find these issues in your code.

Don’t forget to learn all about code performance for Microsoft .NET by going to the Code & App Performance Page: https://bit.ly/CodeAppPerformance.

I hope you will support this site by buying dotNetDave a cup of coffee (he works really hard on these articles): https://www.buymeacoffee.com/dotnetdave

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.