shane skiles - github blog

dispose patterns

I always want to refer back to something when implementing a full sync/async disposal pattern in C#. Well, here it is. This implementation has a disposables list to help keep track of everything you want to dispose of.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

/// <summary>
/// Represents example sync/async disposal pattern.
/// </summary>
public class DisposableCore : IDisposable, IAsyncDisposable
{
    private static readonly List<IDisposable> _disposables = [];
    private bool _disposed;

    /// <inheritdoc/>
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    /// <inheritdoc/>
    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);
        Dispose(disposing: false);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Disposes the object and its resources.
    /// </summary>
    /// <param name="disposing">A boolean value indicating whether the method is called from the Dispose method.</param>
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                foreach (var disposable in _disposables)
                {
                    disposable.Dispose();
                }
            }

            /* 
                Dispose unmanaged resources here. 
                This block is executed regardless of the value of 'disposing'.
                It is called from Dispose(), DisposeAsync(), and the finalizer (if implemented).
            */

            _disposed = true;
        }
    }

    /// <summary>
    /// Disposes the object asynchronously.
    /// </summary>
    /// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
    protected virtual async ValueTask DisposeAsyncCore()
    {
        foreach (var disposable in _disposables)
        {
            if (disposable is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync().ConfigureAwait(false);
            }
            else
            {
                disposable.Dispose();
            }
        }

        await Task.CompletedTask.ConfigureAwait(false);
    }
    
#if USE_FINALIZER
    // If a finalizer is needed, it should call Dispose(false) 
    // to release unmanaged resources.
    /// <summary>
    /// Finalizes an instance of the <see cref="DisposableCore"/> class.
    /// </summary>
    ~DisposableCore() => Dispose(false);
#endif
}

© 2026 Shane Skiles