IDisposable vs. Finalizer
Question
Describe the difference between
IDisposableand a finalizer.
Short interview answer
Disposeprovides deterministic cleanup initiated by the consumer. A finalizer is a nondeterministic GC fallback for directly owned unmanaged resources. A finalizer should not repeat allDisposework because managed objects may already be finalized or unavailable.
Detailed answer
Dispose is an explicit ownership boundary. When the caller finishes with a FileStream, a database connection, or a native handle wrapper, it calls Dispose—usually through using—and the resource is released at that point. A finalizer is different: the garbage collector schedules it only after the object becomes unreachable, so there is no predictable time at which it will run.
Consider a type that directly owns a native buffer and also owns a managed diagnostic stream:
sealed class NativeImage : IDisposable
{
private IntPtr _buffer; // Allocated by a native API.
private readonly Stream _trace; // A managed dependency.
public void Dispose()
{
_trace.Dispose(); // Safe: the consumer controls the timing.
FreeNativeBuffer();
GC.SuppressFinalize(this); // No finalizer pass is now needed.
}
~NativeImage()
{
FreeNativeBuffer(); // Fallback only: do not use _trace here.
}
private void FreeNativeBuffer()
{
if (_buffer == IntPtr.Zero) return;
NativeApi.Free(_buffer);
_buffer = IntPtr.Zero;
}
}
using var image = new NativeImage(...); calls Dispose when execution leaves the scope, including when an exception is thrown. If the caller forgets, the finalizer can eventually free the directly owned native buffer, but it must not rely on _trace: that managed object may already have been finalized. Prefer SafeHandle for unmanaged handle ownership where possible; it avoids many reasons to write a finalizer yourself.