Поделиться через


Финализаторы (руководство по программированию в C#)

Методы завершения (также называемые деструкторами) используются для любой необходимой окончательной очистки, когда сборщик мусора собирает экземпляр класса. В большинстве случаев можно избежать написания финализатора с помощью System.Runtime.InteropServices.SafeHandle или производных классов для упаковки любого неуправляемого дескриптора.

Замечания

  • В структурах нельзя определять финализаторы. Они применяются только в классах.
  • Каждый класс может иметь только один финализатор.
  • Финализаторы не могут быть унаследованы или перегружены.
  • Финализаторы невозможно вызвать. Они запускаются автоматически.
  • Финализатор не принимает модификаторов и не имеет параметров.

Например, ниже показано объявление метода завершения для класса Car.

class Car
{
    ~Car()  // finalizer
    {
        // cleanup statements...
    }
}

Метод завершения можно также реализовать как определение тела выражения, как показано в следующем примере.

public class Destroyer
{
   public override string ToString() => GetType().Name;

   ~Destroyer() => Console.WriteLine($"The {ToString()} finalizer is executing.");
}

Финализатор неявно вызывает метод Finalize для базового класса объекта. В связи с этим вызов финализатора неявно преобразуется в следующий код:

protected override void Finalize()
{
    try
    {
        // Cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}

Это означает, что метод Finalize вызывается рекурсивно для всех экземпляров цепочки наследования начиная с самого дальнего и заканчивая самым первым.

Примечание.

Пустые финализаторы использовать нельзя. Если класс содержит финализатор, то в очереди Finalize создается запись. Эта очередь обрабатывается сборщиком мусора. Когда сборщик мусора обрабатывает очередь, он вызывает каждый финализатор. Ненужные финализаторы, включая пустые финализаторы, финализаторы, которые вызывают только финализатор базового класса, или финализаторы, которые вызывают только условно генерируемые методы, приводят к ненужной потере производительности.

Программист не имеет контроля над тем, когда вызывается финализатор; сборщик мусора решает, когда его вызвать. Сборщик мусора проверяет наличие объектов, которые больше не используются приложением. Если он считает, что какой-либо объект требует уничтожения, то вызывает метод завершения (при наличии) и освобождает память, используемую для хранения этого объекта. Принудительная сборка мусора возможна вызовом Collect, но в большинстве случаев этого следует избегать из-за потенциальных проблем с производительностью.

Примечание.

От конкретной реализации .NET зависит, выполняются ли методы завершения как часть завершения работы приложения. При завершении работы приложения .NET Framework прилагает все возможные усилия для вызова финализаторов объектов, которые еще не были собраны сборщиком мусора, если такая очистка не была подавлена (например, вызовом метода библиотеки GC.SuppressFinalize). .NET 5 (включая .NET Core) и более поздние версии не вызывают финализаторы при завершении приложения. Дополнительные сведения об этой проблеме см. в Проблема dotnet/csharpstandard #291, рассмотренная на сайте GitHub.

Если необходимо надежно выполнить очистку при завершении работы приложения, зарегистрируйте обработчик для события System.AppDomain.ProcessExit. Этот обработчик обеспечит вызов IDisposable.Dispose() (или, IAsyncDisposable.DisposeAsync()) для всех объектов, для которых требуется очистка перед выходом из приложения. Поскольку нельзя вызвать Finalize напрямую и нельзя гарантировать, что сборщик мусора вызовет все финализаторы до выхода, необходимо использовать Dispose или DisposeAsync, чтобы освободить ресурсы.

Использование финализаторов для освобождения ресурсов

В целом язык C# не требует управления памятью в той степени, в какой это требуется в случае разработки кода на языке, не рассчитанном на среду выполнения со сборкой мусора. Это связано с тем, что сборщик мусора платформы .NET неявным образом управляет выделением и высвобождением памяти для объектов. Однако при инкапсуляции приложением неуправляемых ресурсов, например окон, файлов и сетевых подключений, для высвобождения этих ресурсов следует использовать методы завершения. Когда объект подлежит завершению, сборщик мусора инициирует выполнение метода Finalize этого объекта.

Предупреждение

Не обращаться к элементам управляемого объекта из средства завершения. Во время завершения управляемые объекты уже могут быть удалены, что делает их недоступными или в недопустимом состоянии. Доступ только к неуправляемым ресурсам непосредственно из финализаторов.

Явное освобождение ресурсов

В случае, когда приложением используется ценный внешний ресурс, также рекомендуется обеспечить способ высвобождения этого ресурса явным образом, прежде чем сборщик мусора освободит объект. Для высвобождения ресурса реализуется метод Dispose интерфейса IDisposable, который выполняет необходимую для объекта очистку. Это может значительно повысить производительность приложения. Даже при наличии явного управления ресурсами, финализатор становится резервным средством для очистки ресурсов, если вызов метода Dispose не удастся выполнить.

Дополнительные сведения об очистке ресурсов см. в следующих статьях:

Пример

В приведенном ниже примере создаются три класса, образующих цепочку наследования. Класс First является базовым, класс Second является производным от класса First, а класс Third является производным от класса Second. Все три имеют финализаторы. В Main создается экземпляр наиболее производного класса. Выходные данные этого кода зависят от того, на какую реализацию .NET нацелено приложение:

  • .NET Framework: выходные данные показывают, что методы завершения для трех классов вызываются автоматически при завершении работы приложения, в порядке от наиболее производного до наименее производного.
  • .NET 5 (включая .NET Core) или более поздняя версия: выходные данные отсутствуют, так как эта реализация .NET не вызывает финализаторы при завершении работы приложения.
class First
{
    ~First()
    {
        System.Diagnostics.Trace.WriteLine("First's finalizer is called.");
    }
}

class Second : First
{
    ~Second()
    {
        System.Diagnostics.Trace.WriteLine("Second's finalizer is called.");
    }
}

class Third : Second
{
    ~Third()
    {
        System.Diagnostics.Trace.WriteLine("Third's finalizer is called.");
    }
}

/* 
Test with code like the following:
    Third t = new Third();
    t = null;

When objects are finalized, the output would be:
Third's finalizer is called.
Second's finalizer is called.
First's finalizer is called.
*/

Спецификация языка C#

Дополнительные сведения см. в разделе "Методы завершения" спецификации языка C#.

См. также