В предыдущих статьях мы выяснили алгоритм работы сборщика мусора. Мы узнали, что перед удалением объекта, CLR определяет структуру ссылок на объект и при их реальном отсутствии помечает объект на удаление. Это первая фаза удаления объекта. Следующая фаза начинается с помещения этих объектов в очередь процесса, называемого финализацией. Ну а за тем сборщик мусора выполняет процесс финализации, в ходе которого для каждого объекта вызывается метод Finalize(). Данный метод имеется у любого объекта, унаследованного от System.Object. Мы помним, что все объекты, в конечном итоге, наследуются от типа Object.
Сразу, и на будущее, хочу обратить ваше внимание на два очень серьезных момента:
Само по себе удаление объекта занимает большой промежуток времени. Также, метод Finalize() вызывается не из того потока, в котором был создан объект, поскольку сборщик мусора работает в своем отдельном потоке.
Сам механизм сборки мусора инкапсулирован в статическом классе GC.
Собственно, инструкцией GC.Collect() и начинается сборка мусора. Подобная данной инструкция GC.WaitForPendingFinalizers() запускает финализацию всех объектов в очереди. Опять же, если удаление одного объекта занимает достаточный промежуток времени, то при вызове метода GC.WaitForPendingFinalizers() производительность заметно снижается.
Следует сказать, что вам вообще редко когда придется вручную вызывать сборщик мусора. Платформа .NET очень хорошо справляется с задачей своевременного освобождения памяти.
И все-таки, когда же может потребоваться от нас явного вызова сборщика мусора?! Почти всегда при инкапсуляции приложением неуправляемых ресурсов, например окон, файлов, сетевых подключений (чаще всего к БД). Вот для этих неуправляемых объектов, для их высвобождения следует использовать деструкторы. Я думаю, вы уже догадались из названия, что деструкторы по существу являются противоположностью конструкторам.
Итак. Мы выяснили, что удаление объекта выполняется методом Finalize(). Также мы знаем, что любой объект имеет в своем распоряжении метод Finalize(). Но давайте посмотрим на реализацию данного метода в любом объекте:
Public class Object
{
protected virtual void Finalize(){}
}
Как видим, данный метод по умолчанию не делает ничего перед тем, как объект будет удален. Естественно нас это не всегда устраивает. Поэтому данный метод в пользовательских классах следует (если это действительно необходимо) переопределять. Но, на самом деле, c# не позволяет обычным образом переопределить метод Finalize(). Вместо это следует использовать деструкторы:
class MyClass
{
string name;
public MyClass(string name)
{
This.name = name;
}
//определение деструктора
~MyClass()
{
//код по очищению ресурсов
}
}
Сразу следует запомнить следующие правила по определению деструкторов:
- В структурах определение деструкторов невозможно. Они применяются только в классах. Это логично с той позиции, что сборка мусора касается управляемой кучи, то-есть ссылочных объектов, а структуры размещаются в стеке;
- Класс может иметь только один деструктор.
- Деструкторы не могут наследоваться или перегружаться;
- Деструкторы невозможно вызвать. Они запускаются автоматически;
- Деструктор не принимает модификаторы и не имеет параметров.
Еще раз повторим, что метод Finalize() не может бать вызван непосредственно из кода. Это объясняется тем, что понятия деструктора в CLR нет, а c# просто предоставляет нам конструкцию для работы с методом Finalize() именно через деструкторы. Его автоматический вызов происходит сборщиком мусора перед непосредственным удалением объекта из памяти. Таким образом, данный метод будет вызван как при естественном вызове сборки мусора, так и при ручном её вызове методом GC.Collect(). Реализуя метод Finalize() (через деструктор), мы можем гарантировать лишь то, что соответствующий объект сможет очистить неуправляемые ресурсы при сборке мусора.
Теперь подойдем к процессу сборки мусора с другого конца. В момент создания нового объекта в управляемой куче, CLR определяет, реализует ли данный объект пользовательский метод Finalize(). Если да, то объект помечается как финализируемый. Это опять же означает лишь гарантию того, что объект точно освободит неуправляемые ресурсы, но когда это произойдет, зависит от множества факторов и мы не можем на них повлиять.
На последок учтите, что метод Finalize() не должен выдавать исключения, поскольку они не могут обрабатываться приложением и могут привести к непредусмотренному и аварийному завершению работы приложения.
Реализация методов Finalize или деструкторов может отрицательно воздействовать на производительность, и злоупотреблять ими не рекомендуется.