Lessons in this course (11)

7. Finalization, IDisposable and the Dispose pattern

Intermediate★ Must knowLesson 7 of 11

The GC frees managed memory only. Unmanaged resources (file handles, sockets, database connections, OS handles, native memory) must be released explicitly — that's what IDisposable is for.

csharp
public sealed class ReportWriter : IDisposable
{
    private readonly FileStream _file = new("report.txt", FileMode.Create);
    private bool _disposed;

    public void Write(string text) { ObjectDisposedException.ThrowIf(_disposed, this); /* ... */ }

    public void Dispose()
    {
        if (_disposed) return;
        _file.Dispose();          // release what we own
        _disposed = true;
    }
}

using var writer = new ReportWriter();     // Dispose() called at end of scope, even on exceptions

Finalizers (~ClassName()) run on a dedicated finalizer thread some time after the object becomes unreachable:

  • Objects with a finalizer survive at least one extra GC (they go to the f-reachable queue), so they are more expensive.
  • Order and timing are not guaranteed; finalizers may never run on process crash.
  • Use them only as a safety net for directly owned unmanaged handles — and prefer SafeHandle, which already implements this correctly.

The full Dispose pattern (for non-sealed classes that own unmanaged resources):

csharp
public class NativeBuffer : IDisposable
{
    private IntPtr _ptr = Marshal.AllocHGlobal(1024);
    private bool _disposed;

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing) { /* dispose managed members (other IDisposables) */ }
        Marshal.FreeHGlobal(_ptr);    // free unmanaged memory in both paths
        _ptr = IntPtr.Zero;
        _disposed = true;
    }
    public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }   // no need to finalize anymore
    ~NativeBuffer() => Dispose(false);                                   // safety net if Dispose was forgotten
}

IAsyncDisposable / await using for resources whose cleanup is asynchronous (streams flushing to network, DbConnection in async code).

DisposeFinalize
Called byYour code (using)GC finalizer thread
TimingDeterministicNon-deterministic
PurposeRelease managed + unmanaged resources earlyLast-chance release of unmanaged resources
CostCheapExtra GC survival, finalizer thread work