What is the difference between Finalize() and Dispose() methods?

10 minintermediate.NETdisposefinalizememory

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:

AspectDispose()Finalize()
InterfaceIDisposableObject (destructor)
InvocationExplicit (using/Dispose())Automatic (by GC)
TimingDeterministicNon-deterministic
PerformanceFastSlow (promotes to Gen2)
ResourcesManaged + UnmanagedUnmanaged only
ControlDeveloperGarbage Collector
Can be called multiple timesYes (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:

  1. Always implement IDisposable for types that hold disposable resources
  2. Make Dispose() safe to call multiple times
  3. Call GC.SuppressFinalize(this) in Dispose()
  4. Only use finalizers when directly managing unmanaged resources
  5. Prefer SafeHandle over manual finalization
  6. Never throw exceptions from Dispose() or finalizers
  7. Use using statements for automatic disposal
  8. Document disposal requirements clearly

Related Resources