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.