Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

using System;

namespace SimpleFinalize

{

class MyResourceWrapper

{

// Compile-time error!

protected override void Finalize{ }

}

}

На самом деле для обеспечения того же самого эффекта используется синтаксис деструктора (подобный C++). Причина такой альтернативной формы переопределения виртуального метода заключается в том, что при обработке синтаксиса финализатора компилятор автоматически

добавляет внутрь неявно переопределяемого метода
Finalize
много обязательных инфраструктурных элементов (как вскоре будет показано).

Финализаторы C# выглядят похожими на конструкторы тем, что именуются идентично классу, в котором определены. Вдобавок они снабжаются префиксом в виде тильды (

~
). Однако в отличие от конструкторов финализаторы никогда не получают модификатор доступа (они всегда неявно защищенные), не принимают параметров и не могут быть перегружены (в каждом классе допускается наличие только одного финализатора). Ниже приведен специальный финализатор для класса
MyResourceWrapper
, который при вызове выдает звуковой сигнал. Очевидно, такой пример предназначен только для демонстрационных целей. В реальном приложении финализатор только освобождает любые неуправляемые ресурсы и не взаимодействует с другими управляемыми объектами, даже с теми, на которые ссылается текущий объект, т.к. нельзя предполагать, что они все еще существуют на момент вызова этого метода
Finalize
сборщиком мусора.

using System;

// Переопределить System.Object.Finalize

// посредством синтаксиса финализатора.

class MyResourceWrapper

{

// Очистить неуправляемые ресурсы.

// Выдать звуковой сигнал при уничтожении

// (только в целях тестирования)

~MyResourceWrapper => Console.Beep;

}

Если теперь просмотреть код CIL данного финализатора с помощью утилиты

ildasm.exe
, то обнаружится, что компилятор добавил необходимый код для проверки ошибок. Первым делом операторы внутри области действия метода
Finalize
помещены в блок
try
(см. главу 7). Связанный с ним блок
finally
гарантирует, что методы
Finalize
базовых классов будут всегда выполняться независимо от любых исключений, возникших в области
try
.

.method family hidebysig virtual instance void

Finalize cil managed

{

.override [System.Runtime]System.Object::Finalize

// Code size 17 (0x11)

.maxstack 1

.try

{

IL_0000: call void [System.Console]System.Console::Beep

IL_0005: nop

IL_0006: leave.s IL_0010

} // end .try

finally

{

IL_0008: ldarg.0

IL_0009: call instance void [System.Runtime]System.Object::Finalize

IL_000e: nop

IL_000f: endfinally

} // end handler

IL_0010: ret

} // end of method MyResourceWrapper::Finalize

Тестирование класса

MyResourceWrapper
показывает, что звуковой сигнал выдается при выполнении финализатора:

using System;

using SimpleFinalize;

Console.WriteLine("***** Fun with Finalizers *****\n");

Console.WriteLine("Hit return to create the objects ");

Console.WriteLine("then force the GC to invoke Finalize");

// Нажмите клавишу <Enter>, чтобы создать объекты

// и затем заставить сборщик мусора вызвать метод Finalize

// В зависимости от мощности вашей системы

// вам может понадобиться увеличить эти значения.

CreateObjects(1_000_000);

// Искусственно увеличить уровень давления.

GC.AddMemoryPressure(2147483647);

GC.Collect(0, GCCollectionMode.Forced);

GC.WaitForPendingFinalizers;

Console.ReadLine;

static void CreateObjects(int count)

{

MyResourceWrapper[] tonsOfObjects =

new MyResourceWrapper[count];

for (int i = 0; i < count; i++)

{

tonsOfObjects[i] = new MyResourceWrapper;

}

tonsOfObjects = null;

}

На заметку! Единственный способ гарантировать, что такое небольшое консольное приложение принудительно запустит сборку мусора в .NET Core, предусматривает создание огромного количества объектов в памяти и затем установит ссылку на них в null. После запуска этого приложения не забудьте нажать комбинацию клавиш <Ctrl+C>, чтобы остановить его выполнение и прекратить выдачу звуковых сигналов!

Подробности процесса финализации

Важно всегда помнить о том, что роль метода

Finalize
состоит в обеспечении того, что объект .NET Core сумеет освободить неуправляемые ресурсы, когда он подвергается сборке мусора. Таким образом, если вы строите класс, в котором неуправляемая память не применяется (общепризнанно самый распространенный случай), то финализация принесет мало пользы. На самом деле по возможности вы должны проектировать свои типы так, чтобы избегать в них поддержки метода
Finalize
по той простой причине, что финализация занимает время.

При размещении объекта в управляемой куче исполняющая среда автоматически определяет, поддерживает ли он специальный метод

Finalize
. Если да, тогда объект помечается как финализируемый, а указатель на него сохраняется во внутренней очереди, называемой очередью финализации. Очередь финализации — это таблица, обслуживаемая сборщиком мусора, в которой содержатся указатели на все объекты, подлежащие финализации перед удалением из кучи.

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

Finalize
на каждом объекте из упомянутой таблицы при следующей сборке мусора. Итак, действительная финализация объекта требует, по меньшей мере, двух сборок мусора.

Поделиться с друзьями: