Что такое Dispose в C#?

«Что такое Dispose в C#?» — вопрос из категории Управление памятью, который задают на 39% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Dispose — это ключевой метод интерфейса IDisposable, предназначенный для детерминированного освобождения неуправляемых ресурсов. В отличие от управляемой памяти, за которой следит Garbage Collector (GC), за ресурсы вроде файловых дескрипторов, сокетов или графических контекстов нужно явно "договориться" с ОС об их закрытии.

Почему это важно? Без вызова Dispose такие ресурсы могут удерживаться до сборки мусора финализатором, что приводит к утечкам, исчерпанию лимитов ОС и нестабильной работе приложения.

Стандартный паттерн использования:

public class FileLogger : IDisposable
{
    private StreamWriter _writer;
    private bool _disposed = false; // Флаг для защиты от повторного вызова

    public FileLogger(string path)
    {
        _writer = new StreamWriter(path); // Захват неуправляемого ресурса (дескриптора файла)
    }

    public void Log(string message) => _writer.WriteLine(message);

    // Публичная реализация Dispose
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // Сообщаем GC, что финализация не нужна
    }

    // Защищенная виртуальная реализация
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // Освобождаем управляемые ресурсы (если они есть)
                _writer?.Dispose();
            }
            // Здесь освобождаем неуправляемые ресурсы (если они не инкапсулированы в управляемых объектах)
            _disposed = true;
        }
    }

    // Финализатор (резервный механизм на случай, если Dispose не вызвали)
    ~FileLogger() => Dispose(false);
}

// ИСПОЛЬЗОВАНИЕ с блоком 'using' (рекомендуемый способ)
using (var logger = new FileLogger("log.txt"))
{
    logger.Log("Application started");
} // Dispose() вызовется автоматически здесь, даже в случае исключения

Ключевые принципы:

  1. Идемпотентность: Последующие вызовы Dispose() не должны вызывать ошибок.
  2. Освобождение вложенных ресурсов: Если ваш класс владеет другими IDisposable-объектами, вызовите их Dispose().
  3. Блок using — синтаксический сахар, гарантирующий вызов Dispose().