Освобождение ресурсов в C# (Часть 1. Финализаторы)

Доброго времени суток! В этой, и ряде следующих статей, я хочу поговорить об освобождении неуправляемых ресурсов, используемых  в объектах классов, написанных на C#. О чем вообще пойдет речь, ведь в C# объекты удаляются сами (точнее, так называемым сборщиком мусора), т.е. программисту не нужно освобождать память из-под каждого созданного оператором new объекта, как это было в C++. Да, это так, но C# программистам всё ещё приходится работать с так называемыми, неуправляемыми ресурсами, например, файлами, или подключениями к базам данных.

Представьте пример, когда мы пишем некий «класс-обертку» над механизмом работы с файлами. Объекты такого класса будут открывать файлы (скорее всего в своих конструкторах), писать/читать информацию в них, ну и в конце концов должны «закрывать» эти файлы. В C++ такие задачи решались без особых трудностей, там были деструкторы.

Дестркутор в C++ — сущность, обратная по назначению конструктору. Конструктор используется для инициализации создаваемого объекта, а деструктор, для его удаления (если хотите, «подчистки» концов). Таким образом, в C++ можно было использовать связку конструктор/деструктор для выделения/освобождения ресурсов (в конструкторе выделять ресурсы, к примеру, открывать файл, а в деструкторе — освобождать их, т.е. закрывать файл).

В C# есть некие аналоги деструкторам C++, это так называемые финализаторы. Давайте рассмотрим пример класса с конструктором и финализатором:

//Некий класс
class SomeClass
{
    //Это конструктор
    public SomeClass()
    {
        //Тут выделяются какие-то неуправляемы ресурсы
    }

    //А это уже деструктор
    ~SomeClass()
    {
        //Тут освобождаются неуправляемы ресурсы
    }
}

Обратите внимание на выделенную строку. Это и есть объявление финализатора класса. Он имеет такое же имя как и сам класс, но перед ним указывает знак тильды «~». Финализатор в классе может быть только один, и он не должен иметь ни параметров, ни модификатора доступа (public, private и т.п.).

Теперь, мы можем быть уверены, что занятые объектом класса ресурсы будут освобождены! Но есть одно НО (а если на чистоту, то не одно)! Мы не знаем, когда именно будет вызван финализатор конкретного объекта! А вызовется он далеко не сразу. А это значит, что мы не управляем ситуацией, мы ее не контролируем полностью.

Если в C++ мы точно знали, что деструктор вызывается при уничтожении объекта когда мы освобождаем память занятую им через оператор delete, или когда объект выходит из области определения (если он создавался без использования оператора new). В C#, память, занимаемую объектом освобождает сборщик мусора, который живет свой жизнью и работает по определенным, вероятно оптимизированным алгоритмам.

Таким образом, мы получаем следствия:

  • не знаем когда конкретно будут освобождены выделенные в конструкторе ресурсы (они могут быть заняты еще долгое время);
  • не можем использовать никакие другие объекты внутри финализатора (ведь мы не знаем в каком состоянии они будут находиться в момент вызова финализатора).

Плюс, есть ещё один побочный эффект, считается, что объекты, имеющие финализаторы «уничтожаются» сборщиком мусора дольше тех, который не имеют финализоторов (естественно, при прочих равных).

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

Добавить комментарий