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

ЖАНРЫ

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

Console.WriteLine("\nGeneration of refToMyCar is: {0}",

GC.GetGeneration(refToMyCar));

// Создать большое количество объектов для целей тестирования.

object[] tonsOfObjects = new object[50000];

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

{

tonsOfObjects[i] = new object;

}

// Выполнить сборку мусора только для объектов поколения 0.

Console.WriteLine("Force Garbage Collection");

GC.Collect(0, GCCollectionMode.Forced);

GC.WaitForPendingFinalizers;

//
Вывести поколение refToMyCar.

Console.WriteLine("Generation of refToMyCar is: {0}",

GC.GetGeneration(refToMyCar));

// Посмотреть, существует ли еще tonsOfObjects[9000].

if (tonsOfObjects[9000] != null)

{

Console.WriteLine("Generation of tonsOfObjects[9000] is: {0}",

GC.GetGeneration(tonsOfObjects[9000]));

}

else

{

Console.WriteLine("tonsOfObjects[9000] is no longer alive.");

// tonsOfObjects[9000] больше не существует

}

// Вывести количество проведенных сборок мусора для разных поколений.

Console.WriteLine("\nGen 0 has been swept {0} times",

GC.CollectionCount(0)); // Количество сборок для поколения 0

Console.WriteLine("Gen 1 has been swept {0} times",

GC.CollectionCount(1)); // Количество сборок для поколения 1

Console.WriteLine("Gen 2 has been swept {0} times",

GC.CollectionCount(2)); // Количество сборок для поколения 2

Console.ReadLine;

Здесь в целях тестирования преднамеренно был создан большой массив типа

object
(состоящий из 50000 элементов). Ниже показан вывод программы:

***** Fun with System.GC *****

Estimated bytes on heap: 75760

This OS has 3 object generations.

Zippy is going 100 MPH

Generation of refToMyCar is: 0

Forcing Garbage Collection

Generation of refToMyCar is: 1

Generation of tonsOfObjects[9000] is: 1

Gen 0 has been swept 1 times

Gen 1 has been swept 0 times

Gen 2 has been swept 0 times

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

Построение финализируемых объектов

В главе 6 вы узнали, что в самом главном базовом классе .NET Core,

System.Object
, определен виртуальный метод по имени
Finalize
. В своей стандартной реализации он ничего не делает:

// System.Object

public class Object

{

...

protected virtual void Finalize {}

}

За

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

На заметку! Переопределять метод

Finalize
в типах структур не разрешено. Подобное ограничение вполне логично, поскольку структуры являются типами значений, которые изначально никогда не размещаются в куче и, следовательно, никогда не подвергаются сборке мусора. Тем не менее, при создании структуры, которая содержит неуправляемые ресурсы, нуждающиеся в очистке, можно реализовать интерфейс
iDisposable
(вскоре он будет описан). Вспомните из главы 4, что структуры
ref
и структуры
ref
, допускающие только чтение, не могут реализовывать какой-либо интерфейс, но могут реализовывать метод
Dispose
.

Разумеется, вызов метода

Finalize
будет происходить (в итоге) во время "естественной" сборки мусора или в случае ее принудительного запуска внутри кода с помощью
GC.Collect
. В предшествующих версиях .NET (но не в .NET Core) финализатор каждого объекта вызывался при окончании работы приложения. В .NET Core нет никаких способов принудительного запуска финализатора даже при завершении приложения.

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

В рамках платформы .NET Core неуправляемые ресурсы получаются путем прямого обращения к API-интерфейсу операционной системы с применением служб вызова платформы (Platform Invocation Services — P/Invoke) или в сложных сценариях взаимодействия с СОМ. С учетом сказанного можно сформулировать еще одно правило сборки мусора.

Правило. Единственная серьезная причина для переопределения метода

Finalize
связана с использованием в классе C# неуправляемых ресурсов через P/Invoke или сложные задачи взаимодействия с СОМ (обычно посредством разнообразных членов типа
System.Runtime.InteropServices.Marshal
). Это объясняется тем, что в таких сценариях производится манипулирование памятью, которой исполняющая среда управлять не может.

Переопределение метода System.Object.Finalize

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

SimpleFinalize
и вставим в него класс
MyResourceWrapper
, в котором используется неуправляемый ресурс (каким бы он ни был). Теперь необходимо переопределить метод
Finalize
. Как ни странно, для этого нельзя применять ключевое слово
override
языка С#:

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