A lot of you have seen this interface being used for resource management. Let’s try and explain how this interface provides a standardized way to release resources properly and avoid memory leaks.

What is IDisposable?

IDisposable is an interface that contains a single method: Dispose(). It is designed to allow developers to release unmanaged resources deterministically. In .NET, garbage collection handles memory management automatically, but for non-memory resources, such as database connections, file handles, and sockets, we need to manually clean up to avoid resource leaks.

Here’s the simple signature of the IDisposable interface:

public interface IDisposable
{
    void Dispose();
}

When to Implement IDisposable

You should implement IDisposable when your class holds unmanaged resources or uses other IDisposable objects. Some common scenarios include:

  • Working with file streams or network streams.
  • Utilizing database connections or external services.
  • Interfacing with unmanaged code or libraries.

If your class is only dealing with managed memory, then typically, you don’t need to implement IDisposable.

The simplest implementation of IDisposable looks like this:

public class MyResource : IDisposable
{
    private readonly FileStream _fileStream;

    public MyResource(string filePath)
    {
        _fileStream = new FileStream(filePath, FileMode.Open);
    }

    public void Dispose()
    {
        _fileStream?.Dispose();
    }
}

Using the Dispose Pattern Correctly

The “full” Dispose pattern is more complex so let’s give another example before we get to how you should be using it:

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

    private readonly FileStream _fileStream;

    public MyResource(string filePath)
    {
        _fileStream = new FileStream(filePath, FileMode.Open);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // Prevents the finalizer from running so disposing won't happen twice
    }

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

        if (disposing)
        {
            _fileStream?.Dispose();
        }
        
        _disposed = true;
    }

    ~MyResource()
    {
        Dispose(false);
    }
}

Explanation:

  • Dispose(bool disposing): Separates managed and unmanaged resource cleanup. If disposing is true, you dispose of managed resources.
  • GC.SuppressFinalize(this): Prevents the finalizer from running if the resource has already been cleaned up manually.
  • Finalizer (~Destructor): This is a safety net to release unmanaged resources if Dispose is not called explicitly. It should only call Dispose(false).

That’s all great BUT, THE way you should dispose your object is by having a using statement.

The using Statement

In most cases, classes that implement IDisposable are used within a using statement, which ensures the Dispose method is called automatically, even if an exception occurs. The using statement is syntactic sugar for a try-finally:

var resource = new MyResource("myfile.txt");
try
{
    // Use the resource
}
finally
{
    resource.Dispose();
}

Starting with C# 8.0, you can use the “using declaration” syntax for better readability:

using var resource = new MyResource("myfile.txt");
// Use the resource

A common mistake is trying to access the object outside its using scope:

StreamReader reader;

using (reader = new StreamReader("file.txt"))
{
    // Use the reader within the block
    string content = reader.ReadToEnd();
}

Console.WriteLine(reader.ReadLine()); // This will throw an ObjectDisposedException

Same thing goes with the C# 8.0 using declaration, it will be called automatically at the end of the enclosing scope (typically at the end of the method). If you’re not careful about where the using declaration is placed, you could accidentally access a disposed object later in your code.

To sum up, let see some common mistakes I have seen on many applications:

Mistake #1: Not Implementing IDisposable Correctly If your class holds resources that need explicit cleanup, failing to implement IDisposable can lead to memory leaks and performance issues.

Mistake #2: Not Calling Dispose Always ensure Dispose is called either explicitly or via a using statement. Forgetting to do this can leave resources open longer than needed.

Mistake #3: Forgetting GC.SuppressFinalize If your class has a finalizer, make sure to call GC.SuppressFinalize(this) within Dispose to avoid unnecessary overhead.

Conclusion

Implementing IDisposable is crucial for managing unmanaged resources and ensuring your .NET applications don’t have any memory leaks. The proper use of the Dispose pattern and features like using statements, can help you write clean, maintainable code that safely handles resource cleanup.

Affiliate promo

If you love learning new stuff and want to support me, consider buying a course from Dometrain using this link: Browse courses – Dometrain. Thank you!

Leave a comment

Trending