What is the difference between Finalize() and Dispose() methods?
Quick Answer
Dispose() is part of IDisposable interface, called explicitly by developer for deterministic cleanup of managed and unmanaged resources. Finalize() (destructor) is called by Garbage Collector for non-deterministic cleanup, primarily for unmanaged resources. Dispose() is faster and should be preferred. Use using statements for automatic disposal.
Detailed Answer
Both methods are related to resource cleanup, but they serve different purposes and are called at different times.
Dispose():
- Part of IDisposable interface
- Called explicitly by developer
- Deterministic cleanup (you control when)
- Used for managed and unmanaged resources
- Should be called as soon as resource is no longer needed
- Can be called multiple times (should be idempotent)
Finalize():
- Also called destructor (~ClassName)
- Called by Garbage Collector
- Non-deterministic cleanup (GC decides when)
- Used primarily for unmanaged resources
- Called when object is being collected
- Cannot be called explicitly
- Delays garbage collection (performance impact)
Basic Example:
public class ResourceHolder : IDisposable
{
private IntPtr unmanagedResource;
private FileStream managedResource;
private bool disposed = false;
public ResourceHolder()
{
unmanagedResource = // Allocate unmanaged resource
managedResource = new FileStream("file.txt", FileMode.Open);
}
// IDisposable implementation - called explicitly
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Tell GC not to call finalizer
}
// Finalizer - called by GC
~ResourceHolder()
{
Dispose(false);
}
// Protected dispose method - actual cleanup logic
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources
managedResource?.Dispose();
}
// Free unmanaged resources
if (unmanagedResource != IntPtr.Zero)
{
// Free unmanaged resource
unmanagedResource = IntPtr.Zero;
}
disposed = true;
}
}
}
Key Differences:
| Aspect | Dispose() | Finalize() |
|---|---|---|
| Interface | IDisposable | Object (destructor) |
| Invocation | Explicit (using/Dispose()) | Automatic (by GC) |
| Timing | Deterministic | Non-deterministic |
| Performance | Fast | Slow (promotes to Gen2) |
| Resources | Managed + Unmanaged | Unmanaged only |
| Control | Developer | Garbage Collector |
| Can be called multiple times | Yes (should handle) | No (once by GC) |
Using Dispose() - Recommended Pattern:
// Manual disposal
ResourceHolder resource = new ResourceHolder();
try
{
// Use resource
}
finally
{
resource.Dispose();
}
// Better - using statement
using (ResourceHolder resource = new ResourceHolder())
{
// Use resource
} // Dispose() called automatically
// C# 8+ - using declaration
using ResourceHolder resource = new ResourceHolder();
// Use resource
// Dispose() called at end of scope
Full Dispose Pattern (IDisposable):
public class DatabaseConnection : IDisposable
{
private SqlConnection connection;
private SqlCommand command;
private bool disposed = false;
public DatabaseConnection(string connectionString)
{
connection = new SqlConnection(connectionString);
command = connection.CreateCommand();
}
// Public dispose method
public void Dispose()
{
Dispose(disposing: true);
// Tell GC not to call finalizer
// Improves performance by avoiding finalization queue
GC.SuppressFinalize(this);
}
// Protected virtual dispose method
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources
command?.Dispose();
connection?.Dispose();
}
// Free unmanaged resources here (if any)
// Note: Most .NET types handle their own unmanaged resources
disposed = true;
}
}
// Optional: Finalizer (only if holding unmanaged resources directly)
~DatabaseConnection()
{
Dispose(disposing: false);
}
// Helper method to prevent use after disposal
private void ThrowIfDisposed()
{
if (disposed)
throw new ObjectDisposedException(GetType().Name);
}
public void ExecuteQuery(string query)
{
ThrowIfDisposed();
command.CommandText = query;
command.ExecuteNonQuery();
}
}
Why Finalize() is Slow:
// Object lifecycle WITHOUT finalizer:
// 1. Object created in Gen0
// 2. GC collects Gen0
// 3. Object memory reclaimed immediately
// Object lifecycle WITH finalizer:
// 1. Object created in Gen0
// 2. GC collects Gen0
// 3. Object moved to finalization queue
// 4. Object promoted to Gen1
// 5. Finalizer thread runs ~ClassName()
// 6. GC collects Gen1
// 7. Object memory finally reclaimed
// Finalizers cause objects to survive at least one GC cycle!
When to Use Each:
Use Dispose() when:
- Working with files, database connections, network connections
- Using any IDisposable resource
- You need immediate cleanup
- Most common scenario
Use Finalize() when:
- Directly managing unmanaged resources (rare in modern C#)
- P/Invoke scenarios with unmanaged memory
- Safety net for forgotten Dispose() calls
Modern Approach - Usually No Finalizer Needed:
public class ModernResourceHolder : IDisposable
{
private readonly FileStream _file;
private bool _disposed;
public ModernResourceHolder(string path)
{
_file = new FileStream(path, FileMode.Open);
}
public void Dispose()
{
if (_disposed)
return;
_file?.Dispose(); // FileStream handles its own finalization
_disposed = true;
}
// No finalizer needed!
// FileStream already has its own finalizer
}
SafeHandle - Better than Finalizer:
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public class UnmanagedResource : IDisposable
{
// SafeHandle provides reliable finalization
private SafeFileHandle _handle;
private bool _disposed;
public UnmanagedResource(string path)
{
_handle = // Create safe handle
}
public void Dispose()
{
if (_disposed)
return;
_handle?.Dispose(); // SafeHandle handles finalization
_disposed = true;
}
// No finalizer needed!
// SafeHandle has reliable finalization built-in
}
Common Mistakes:
// WRONG - Accessing managed objects in finalizer
~BadClass()
{
// Dangerous! Managed objects might already be finalized
managedResource.Dispose(); // Could throw exception!
}
// WRONG - Not checking if already disposed
public void Dispose()
{
resource.Dispose(); // Might throw if called twice
}
// WRONG - Forgetting to suppress finalization
public void Dispose()
{
// Clean up
// Missing: GC.SuppressFinalize(this);
}
// WRONG - Throwing exceptions in Dispose
public void Dispose()
{
throw new Exception(); // Never throw from Dispose!
}
Best Practices:
- Always implement IDisposable for types that hold disposable resources
- Make Dispose() safe to call multiple times
- Call GC.SuppressFinalize(this) in Dispose()
- Only use finalizers when directly managing unmanaged resources
- Prefer SafeHandle over manual finalization
- Never throw exceptions from Dispose() or finalizers
- Use using statements for automatic disposal
- Document disposal requirements clearly