← Назад к вопросам

Когда вызывается Finalize?

2.0 Middle🔥 291 комментариев
#Память и Garbage Collector

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда вызывается Finalize в C#

Основы: Финализатор и сборка мусора

Finalize — это специальный метод деструктора в C#, который вызывается автоматически сборщиком мусора (Garbage Collector) перед удалением объекта из памяти. Это не то же самое, что Dispose().

Жизненный цикл объекта с Finalize

Этап 1: Создание объекта

public class MyResource
{
    public MyResource()
    {
        Console.WriteLine("Конструктор: объект создан");
    }
    
    ~MyResource()  // Это финализатор (деструктор)
    {
        Console.WriteLine("Finalize: объект удаляется из памяти");
    }
}

// Использование
var obj = new MyResource();  // Выводит: "Конструктор: объект создан"
obj = null;  // Объект становится кандидатом на удаление

Когда Finalize вызывается

Правило 1: После того, как объект становится недостижимым (unreachable)

public class Resource
{
    ~Resource()
    {
        Console.WriteLine("Finalize вызван");
    }
}

{
    var resource = new Resource();
}  // Объект выходит из scope и становится недостижимым

// Когда срабатывает сборка мусора:
// 1. GC определяет, что объект недостижим
// 2. Перемещает объект в очередь финализации (Finalization Queue)
// 3. Специальный поток (Finalizer Thread) вызывает Finalize
// 4. После Finalize объект удаляется из памяти

Правило 2: Сборка мусора срабатывает по условиям, НЕ по времени

// GC срабатывает:
// - Когда закончилась памяь для поколения (Generation)
// - После явного вызова GC.Collect() (антипаттерн!)
// - При нехватке памяти

for (int i = 0; i < 1000000; i++)
{
    var resource = new Resource();
    // Finalize вызывется непредсказуемо - может быть сразу,
    // может быть минутами позже
}

Три поколения (Generations)

Сборщик мусора делит объекты на поколения для эффективности:

  • Gen 0: Новые объекты (очищается часто)
  • Gen 1: Объекты, пережившие одну сборку мусора
  • Gen 2: Долгоживущие объекты (очищается редко)
public class MyResource
{
    ~MyResource()
    {
        // Finalize вызовется только когда сборщик мусора
        // очистит поколение, в котором находится этот объект
        Console.WriteLine("Finalize вызван в неопределённое время");
    }
}

Важно: НЕОПРЕДЕЛЁННОСТЬ

Финализатор вызывается в неопределённое время!

var resource = new Resource();
resource = null;

// Когда вызовется Finalize? 
// - Не сразу!
// - Не когда закончится метод
// - Когда сборщик мусора решит, что пора чистить память

Console.WriteLine("Код продолжает работать");
// Finalize может вызваться ДО этого print, А МОЖЕТ И ПОСЛЕ

GC.Collect();  // Форсируем сборку мусора (плохая практика)
GC.WaitForPendingFinalizers();  // Ждём завершения финализации
Console.WriteLine("Теперь Finalize точно вызван");

Правильный способ: IDisposable

Не полагайся на Finalize! Используй IDisposable для детерминированного освобождения ресурсов:

public class Resource : IDisposable
{
    private bool _disposed = false;
    
    // Явное освобождение (детерминированное)
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);  // Скажи GC: не вызывай Finalize
    }
    
    // Страховка на случай, если забыли Dispose()
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // Освобождаем управляемые ресурсы
                Console.WriteLine("Dispose: освобождены управляемые ресурсы");
            }
            
            // Освобождаем неуправляемые ресурсы
            Console.WriteLine("Dispose: освобождены неуправляемые ресурсы");
            _disposed = true;
        }
    }
    
    // Финализатор вызовется только если забыли Dispose()
    ~Resource()
    {
        Dispose(false);
    }
}

// Правильное использование
using (var resource = new Resource())
{
    // Используем ресурс
}  // Dispose() вызывается ГАРАНТИРОВАННО в конце блока using

Практический пример

Неправильно: полагаемся на Finalize

public class FileReader
{
    private FileStream _stream;
    
    public FileReader(string path)
    {
        _stream = new FileStream(path, FileMode.Open);
    }
    
    ~FileReader()  // Надеемся, Finalize освободит файл...
    {
        _stream?.Dispose();  // Но когда это произойдёт? Неизвестно!
    }
}

var reader = new FileReader("file.txt");
reader = null;  // Файл может остаться заблокированным долго!

Правильно: используем Dispose

public class FileReader : IDisposable
{
    private FileStream _stream;
    private bool _disposed = false;
    
    public FileReader(string path)
    {
        _stream = new FileStream(path, FileMode.Open);
    }
    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            _stream?.Dispose();  // Файл освобождается СРАЗУ
            _disposed = true;
        }
    }
    
    ~FileReader()  // Страховка на случай, если забыли Dispose()
    {
        Dispose(false);
    }
}

// Гарантированное освобождение файла
using (var reader = new FileReader("file.txt"))
{
    // Работаем с файлом
}  // Dispose() вызывается СРАЗУ, файл освобождается немедленно

Краткие выводы

  1. Finalize вызывается в неопределённое время — только после сборки мусора
  2. Не используй Finalize для критичных ресурсов — используй IDisposable и using
  3. Finalize нужен только для страховки — на случай, если забыли Dispose()
  4. GC.Collect() — антипаттерн — не вызывай его явно
  5. Используй using statement — гарантирует вызов Dispose() в конце блока

Золотое правило: Если класс управляет неуправляемыми ресурсами, реализуй IDisposable и используй using statement, а не полагайся на Finalize.