[C#] Implementing the IDisposable Interface to Properly Release Resources

While the .NET Garbage Collector (GC) automates memory management, it does not manage “unmanaged resources” such as file handles, database connections, or network sockets. To properly release these resources, a class must implement the IDisposable interface.

Here, I will explain how to implement safe and reliable resource release based on the “Dispose Pattern” recommended by Microsoft.

目次

Table of Contents

  1. Basic Concepts of the Dispose Pattern
  2. Practical Code Example: Implementing the Dispose Pattern
  3. Technical Points and Explanation
    1. Role of Dispose(bool disposing)
    2. GC.SuppressFinalize(this)
    3. Necessity of Finalizers
  4. Summary

Basic Concepts of the Dispose Pattern

To correctly implement IDisposable, you must consider the following elements:

  • Prevention of Double Disposal: Ensure that calling the Dispose method multiple times does not cause an error.
  • Consideration for Derived Classes: Allow inherited classes to add their own resource release logic.
  • Coordination with Finalizers: Ensure that a minimum cleanup (finalizer) runs during GC collection even if the developer forgets to call Dispose.

The implementation pattern centered around the Dispose(bool disposing) method satisfies these requirements.


Practical Code Example: Implementing the Dispose Pattern

The following code implements the complete Dispose Pattern using a class that holds a file stream (managed resource) internally as an example.

using System;
using System.IO;

namespace ResourceManager
{
    class Program
    {
        static void Main()
        {
            // By using the using statement,
            // Dispose() is automatically called when exiting the block.
            using (var handler = new FileRequestHandler("sample.txt"))
            {
                handler.WriteLog("Processing started.");
            }
            // Resources are released here.
        }
    }

    /// <summary>
    /// Resource management class implementing IDisposable
    /// </summary>
    public class FileRequestHandler : IDisposable
    {
        // Flag to detect multiple disposals
        private bool _disposed = false;

        // Internal managed resources
        private FileStream _stream;
        private StreamWriter _writer;

        public FileRequestHandler(string filePath)
        {
            // Resource allocation
            _stream = new FileStream(filePath, FileMode.Create);
            _writer = new StreamWriter(_stream);
        }

        public void WriteLog(string message)
        {
            // Do not allow operations if already disposed
            if (_disposed)
            {
                throw new ObjectDisposedException(nameof(FileRequestHandler));
            }
            _writer.WriteLine(message);
        }

        // --- Implementation of IDisposable ---

        /// <summary>
        /// Method explicitly called by the user (or using statement)
        /// </summary>
        public void Dispose()
        {
            // true: Indicates called from user code
            Dispose(true);

            // Suppress the execution of the finalizer (destructor) since resources are released.
            // This minimizes impact on GC performance.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Actual resource release logic (can be overridden by derived classes)
        /// </summary>
        /// <param name="disposing">
        /// true: Called from Dispose() (Release both managed and unmanaged resources)
        /// false: Called from Finalizer (Release only unmanaged resources)
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            // Do nothing if already disposed
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                // Release "Managed Resources" here.
                // (e.g., Stream or SqlConnection held as member variables)
                if (_writer != null)
                {
                    _writer.Dispose();
                    _writer = null;
                }
                if (_stream != null)
                {
                    _stream.Dispose();
                    _stream = null;
                }
            }

            // Release "Unmanaged Resources" here.
            // (e.g., Handle manipulation via IntPtr, calling CloseHandle in Win32 API)
            // No description here as there are no direct unmanaged resources in this example.

            // Set the disposed flag
            _disposed = true;
        }

        /// <summary>
        /// Finalizer (Destructor)
        /// Functions as insurance in case Dispose() was not called.
        /// </summary>
        ~FileRequestHandler()
        {
            // false: Indicates called from the Garbage Collector
            Dispose(false);
        }
    }
}

Technical Points and Explanation

1. Role of Dispose(bool disposing)

This method is the core of the pattern. The processing branches depending on the disposing flag argument.

  • disposing == true (Called from Dispose()):
    • This is when the developer calls it explicitly.
    • It is safe to call Dispose() on internally held “managed resources (other IDisposable objects).”
    • Also release unmanaged resources.
  • disposing == false (Called from Finalizer):
    • Called immediately before the object is destroyed by the GC.
    • Important: You must not touch other managed resources here. Those objects might have already been destroyed by the GC.
    • Only release unmanaged resources to prevent application crashes.

2. GC.SuppressFinalize(this)

If resource release is completed within the Dispose() method, there is no need to run the finalizer. Calling GC.SuppressFinalize(this) notifies the GC that “calling the finalizer for this object is unnecessary,” improving memory collection efficiency.

3. Necessity of Finalizers

In modern C# development, cases requiring the implementation of a finalizer (~ClassName) are rare. Standard .NET classes like FileStream and SqlConnection already implement finalizers internally. Therefore, if you are creating a class that simply wraps these, it is often sufficient to just call their Dispose() methods within your Dispose(), without defining a finalizer. Finalizers are mandatory only when creating classes that directly handle raw handles like IntPtr.


Summary

Implementing IDisposable is essential for preventing memory leaks and file locking issues. By correctly implementing the “Dispose Pattern,” you ensure both safety (via the finalizer) in case the developer forgets to release resources, and performance (via SuppressFinalize) when resources are explicitly released. Be sure to apply this pattern when designing classes that hold external resources.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次