In this article, we will show how to correctly implement the IDisposable interface. We will focus on key aspects with a clear explanation.

The IDisposable interface in managed code gives us a tool to manage resource cleanup. By implementing IDisposable, we can also release any unmanaged resources held by our object.

As a C# programmer, we should be able to correctly implement the IDisposable interface using best practices, including the Dispose design pattern.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
To download the source code for this article, you can visit our GitHub repository.

Let’s dive in.

The Purpose of the IDisposable Interface

The IDisposable interface is primarily used to release resources in a timely fashion.

The .NET Garbage Collector (GC) manages memory excellently by unpredictably releasing unused variables. We could exhaust some scarce resources before GC kicks in; thus, we must handle that differently.

These resources involve handling files, connecting to databases and networks, and managing other unmanaged resources.

We must explicitly release these resources to avoid resource leaks and performance issues.

Basic Implementation of IDisposable

We can implement the IDisposable interface straightforwardly in most scenarios. If our class implements IDisposable, we must provide a Dispose() method. In that method, we should include all the necessary logic for releasing unmanaged resources and references to other disposable managed objects.

We must release a resource deterministically without waiting for the GC to act.

In our case, our class MyClass has a field of a type that we should dispose of. Because of that, our class should implement the IDisposable interface:

public sealed class MyClass : IDisposable
{
    private readonly IManagedResource _managedResource;

    public void Dispose()
    {
        Console.WriteLine($"Called {nameof(MyClass)}.{nameof(Dispose)}");

        _managedResource.Dispose();
    }
}

As we can see, we only need to implement one method Dispose() and call the Dispose() method on our _managedResource object inside of it. This version works well for sealed classes.

To improve our implementation slightly, we should dispose of our managed resources only once:

public sealed class MyClass : IDisposable
{
    private bool _disposed = false;

    private readonly IManagedResource _managedResource;

    public void Dispose()
    {
        Console.WriteLine($"Called {nameof(MyClass)}.{nameof(Dispose)}");

        if (_disposed)
        {
            return;
        }

        _managedResource.Dispose();

        _disposed = true;
    }
}

Now, we add a new field _disposed and check inside the Dispose() method if we have already disposed of this object (our flag will be true). If not, dispose of our resources and set this flag.

The Dispose Pattern

Imagine that our project is getting bigger, and we must support further inheritance. We do that by making the IDisposable interface more general.

Let’s start with our new class MyParentClass. We are using what we already learned with a small twist:

public class MyParentClass : IDisposable
{
    private bool _disposed = false;

    private readonly ManagedResource _parentManagedResource;


    public void Dispose()
    {
        Dispose(true);

        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        Console.WriteLine($"Called {nameof(MyParentClass)}.{nameof(Dispose)}");

        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
            _parentManagedResource.Dispose();
        }

        _disposed = true;
    }
}

Here, we move our disposing logic to a separate virtual method, also named Dispose(), which child classes can override. That method has a parameter called disposing. We will dispose of any managed resources by setting disposing to true. Our new method will run when the public method calls it. By doing this, we successfully achieve the implementation of the IDisposable interface.

Let’s now implement our child class:

public class MyChildClass : MyParentClass
{
    private bool _disposed = false;

    private readonly ManagedResource _childManagedResource;

    protected override void Dispose(bool disposing)
    {
        Console.WriteLine($"Called {nameof(MyChildClass)}.{nameof(Dispose)}");

        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
            _childManagedResource.Dispose();
        }

        _disposed = true;

        base.Dispose(disposing);
    }
}

There are key points for us regarding inheritance and IDisposable. First, we should override the inherited Dispose() method in derived classes to dispose of the derived class’s specific resources.

Also, we should always call the base class’s Dispose() method to ensure the proper release of all resources in the hierarchy.

Finally, we should set a private _disposed field in base and derived classes to track if we have already disposed of the resource.

We call this pattern the Dispose Pattern. It ensures classes in hierarchies dispose of resources properly and efficiently, allowing each class to clean up its resources correctly without interfering with resource disposal in the base class.

Automatic Invocation of the IDisposable

We often ask if the system automatically calls the Dispose() method. The answer is no.

While the .NET GC is efficient at managing memory, it does not automatically call the Dispose() method of the IDisposable interface. Therefore, we must explicitly call the Dispose() method or use the using statement for implicit disposal.

To prove that is the case, let’s review a couple of examples which we will add to our Program class.

First, we will use an object of our class without calling the Dispose() method or the using statement:

Console.WriteLine("Dispose not called...");

var managedResource = new ManagedResource();
var myClass = new MyClass(managedResource);

myClass.DoSomething();

Here, we create our managed resource, but we do not call Dispose() method not explicitly nor implicitly.

Our results confirm this:

Dispose not called...
Called MyClass.DoSomething

Now, let’s call the Dispose() method explicitly:

Console.WriteLine("Dispose called explicitly...");

var managedResource = new ManagedResource();
var myClass = new MyClass(managedResource);

myClass.DoSomething();
myClass.Dispose();

This time, after we call the DoSomething() method, we make an explicit call to the Dispose() method.

Now, seeing the results, we can confirm that the Dispose() method was indeed called:

Dispose called explicitly...
Called MyClass.DoSomething
Called MyClass.Dispose
Called ManagedResource.Dispose

Alternatively, we can use the using statement:

Console.WriteLine("Dispose called implicitly...");

var managedResource = new ManagedResource();
using var myClass = new MyClass(managedResource);
myClass.DoSomething();

In most cases, this is the preferred way of using disposable objects. Even though we are not calling Dispose() method by ourselves, it can still be executed.

We can verify that it works and that Dispose() is called:

Dispose called implicitly...
Called MyClass.DoSomething
Called MyClass.Dispose
Called ManagedResource.Dispose

Finally, we will verify that our implementation works for the parent/child hierarchy:

Console.WriteLine("Dispose called for the whole hierarchy...");

var parentManagedResource = new ManagedResource();
var childManagedResource = new ManagedResource();

using var childClass = new MyChildClass(parentManagedResource, childManagedResource);
childClass.DoSomething();

This time, we want to release all managed resources for parent and child classes. So the Dispose() method should be executed multiple times, once for the childClass then for its managed resource, and finally for the parent class and its managed resource.

Our results meet our expectations, showing that the Dispose() method was called multiple times as expected:

Dispose called for the whole hierarchy...
Called MyParentClass.DoSomething
Called MyChildClass.Dispose
Called ManagedResource.Dispose
Called MyParentClass.Dispose
Called ManagedResource.Dispose

Best Practices for IDisposable Interface

There are some best practices to consider when we want to correctly implement the IDisposable interface.

We should dispose of IDisposable objects as soon as we can. When using IDisposable objects as instance fields, implementing the IDisposable interface becomes necessary.

Additionally, allowing the Dispose() method to be called multiple times without throwing exceptions is advisable. We should implement IDisposable to support the disposal of resources in a class hierarchy.

Enabling static analysis with rule CA2000, which involves disposing of objects before losing scope, can be beneficial.  Our understanding of the domain and tools is crucial; sometimes, disposing of objects on our own is not advisable, even if they implement the IDisposable interface (such as with the HttClient class in ASP.NET Core).

We should declare a finalizer for cleanup when using unmanaged resources. If our class uses an async disposable field, we should implement the IAsyncDisposable interface. 

Conclusion

Now we know how to correctly implement the IDisposable interface, which helps us with managing resources. This approach ensures proper resource release, preventing leaks and potential performance issues. We should remember that the GC does not automatically call the Dispose() method; thus, we must do so correctly. By following the guidelines, we can ensure that our applications run efficiently and reliably.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!