Что такое деструктор и финализатор в C# и для чего они нужны
Что проверяют
Понимаете ли вы, кто и когда освобождает ресурсы в среде со сборщиком мусора. За вопросом стоит граница между управляемой памятью, за которую отвечает GC, и внешними хэндлами, за которые отвечаете вы. Заодно смотрят, не тянете ли вы в .NET привычки C++.
Как отвечать
Сразу снимите ложное противопоставление: в C# это одно и то же. Синтаксис с тильдой компилятор превращает в переопределение метода Finalize, поэтому «деструктор» это унаследованное от C++ название финализатора.
class Handle : IDisposable
{
private bool disposed;
~Handle() => Dispose(false);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing) { /* управляемые поля */ }
// здесь закрываем неуправляемый хэндл
disposed = true;
}
}Ключевое: момент вызова финализатора не определён. Объект с финализатором при создании попадает в очередь финализации, переживает первую сборку и уходит в старшее поколение, а сам вызов делает отдельный поток. Детерминированное освобождение даёт только IDisposable вместе с using, финализатор это страховка на случай, если Dispose забыли.
Сильный ответ заканчивается практикой: свой финализатор писать почти никогда не нужно, для неуправляемых хэндлов есть SafeHandle, а Dispose обязан быть идемпотентным.
Где спотыкаются
Обещают, что финализатор обязательно отработает. Гарантии нет: при аварийном завершении процесса очередь финализации не разгребается, а зависший финализатор блокирует общий поток и остальные объекты вместе с ним.
Трогают в финализаторе другие управляемые объекты. К этому моменту они могут быть уже собраны, порядок финализации не определён, а необработанное исключение оттуда роняет процесс. Ради этого в шаблоне и появился параметр disposing: по ветке из сборщика чужие объекты не трогаем.
Вешают финализатор на всё подряд. Каждый такой объект живёт дольше и переживает лишнюю сборку, то есть плата берётся с каждого экземпляра. Если класс держит только управляемые поля или SafeHandle, финализатор не нужен вовсе, хватает Dispose.