Есть ли деструкторы в C#?

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

Ответ

Да, в C# есть деструкторы (формально называемые финализаторами), но их семантика и использование кардинально отличаются от деструкторов в C++.

Ключевые отличия и особенности:

  • Синтаксис: Объявляются с тильдой, как в C++: ~ClassName() { ... }.
  • Не детерминированное выполнение: Финализатор вызывается сборщиком мусора (GC) в неопределённый момент после того, как объект стал недостижимым. Программист не может управлять временем его вызова.
  • Не для управления памятью: Основная цель в C# — освобождение неуправляемых ресурсов (например, дескрипторов файлов, сокетов), если это не было сделано явно.

Почему финализаторы — плохая практика в большинстве случаев:

  1. Производительность: Объект с финализатором требует минимум двух проходов GC для очистки, что создает нагрузку.
  2. Ненадёжность: Нет гарантии, что финализатор будет вызван вообще (например, при аварийном завершении процесса).
  3. Сложность: Порядок вызова финализаторов не определён.

Современная альтернатива — шаблон IDisposable: Для детерминированного освобождения ресурсов используется интерфейс IDisposable в сочетании с блоком using.

public class ResourceHolder : IDisposable
{
    private FileStream _fileStream; // Пример неуправляемого ресурса
    private bool _disposed = false;

    // Публичный метод для детерминированной очистки
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // Отменяет вызов финализатора
    }

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

    // Финализатор — страховка на случай, если Dispose не был вызван
    ~ResourceHolder()
    {
        Dispose(false);
    }
}

// Правильное использование
using (var holder = new ResourceHolder())
{
    // работа с ресурсом
} // Dispose() вызывается автоматически здесь

Вывод: Финализаторы существуют, но в 99% случаев управление ресурсами должно осуществляться через IDisposable. Финализатор оставляют как "страховочный" механизм в классе, владеющем критичными неуправляемыми ресурсами.