Phase 24 of 30 · Topic 24.4

Finalizers, `SafeHandle` & the Standard Dispose Pattern

1Concept

Finalizers (`~ClassName()`) delay memory deallocation by two GC cycles because objects must pass through the Finalization Queue and FReachable Queue. `SafeHandle` wraps OS handles without custom finalizers.

2Architecture Diagram

Object with Finalizer ──> Placed in Finalization Queue
                                  │ (Survives GC 1!)
                                  ▼
                     Moved to FReachable Queue ──> Finalizer Thread runs (~ClassName)
                                  │ (Survives GC 2!)
                                  ▼ Reclaimed on 3rd GC Cycle! (Very Slow!)

3Code Example

C# 13 & .NET 9
using System;

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

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // Skips Finalization Queue entirely!
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // Free managed resources
            }
            // Free unmanaged resources
            _disposed = true;
            Console.WriteLine("Disposed cleanly. GC.SuppressFinalize prevented finalizer thread overhead.");
        }
    }

    ~ResourceHolder() => Dispose(false);
}

public class FinalizePatternDemo
{
    public static void Main()
    {
        using (var res = new ResourceHolder())
        {
            // Working with resource
        }
    }
}

4Expected Output

Disposed cleanly. GC.SuppressFinalize prevented finalizer thread overhead.

5Key Takeaways

  • Always call `GC.SuppressFinalize(this)` inside `Dispose()`.
  • Prefer `SafeHandle` over custom finalizers.
  • Never write empty finalizers in C# classes.