{
...
.try
{
...
} // end .try
finally
{
IL_0018: callvirt instance void
[System.Runtime]System.IDisposable::Dispose()
...
} // end handler
IL_001f: ret
} // end of method Program::UsingDeclaration
По сути, это новое средство является "магией" компилятора, позволяющей сэкономить несколько нажатий клавиш. При его использовании соблюдайте осторожность, т.к. новый синтаксис не настолько ясен, как предыдущий.
Создание финализируемых и освобождаемых типов
К настоящему моменту вы видели два разных подхода к конструированию класса, который очищает внутренние неуправляемые ресурсы. С одной стороны, можно применять финализатор. Использование такого подхода дает уверенность в том, что объект будет очищать себя сам во время сборки мусора (когда бы она ни произошла) без вмешательства со стороны пользователя. С другой стороны, можно реализовать интерфейс
IDisposable
и предоставить пользователю объекта способ очистки объекта по окончании работы с ним. Тем не менее, если пользователь объекта забудет вызвать метод
Dispose()
, то неуправляемые ресурсы могут оставаться в памяти неопределенно долго.
Нетрудно догадаться, что в одном определении класса можно смешивать оба подхода, извлекая лучшее из обеих моделей. Если пользователь объекта не забыл вызвать метод
Dispose()
, тогда можно проинформировать сборщик мусора о пропуске процесса финализации, вызвав метод
GC.SuppressFinalize()
. Если же пользователь объекта забыл вызвать
Dispose()
, то объект со временем будет финализирован и получит шанс освободить внутренние ресурсы. Преимущество здесь в том, что внутренние неуправляемые ресурсы будут тем или иным способом освобождены.
Ниже представлена очередная версия класса
MyResourceWrapper
, который теперь является финализируемым и освобождаемым; она определена в проекте консольного приложения C# по имени
FinalizableDisposableClass
:
using System;
namespace FinalizableDisposableClass
{
// Усовершенствованная оболочка для ресурсов.
public class MyResourceWrapper : IDisposable
{
// Сборщик мусора будет вызывать этот метод, если
// пользователь объекта забыл вызвать Dispose().
~MyResourceWrapper()
{
// Очистить любые внутренние неуправляемые ресурсы.
// **Не** вызывать Dispose() на управляемых объектах.
}
// Пользователь объекта будет вызывать этот метод
// для как можно более скорой очистки ресурсов.
public void Dispose()
{
// Очистить неуправляемые ресурсы.
// Вызвать Dispose() для других освобождаемых объектов,
// содержащихся внутри.
// Если пользователь вызвал Dispose(), то финализация
// не нужна, поэтому подавить ее.
GC.SuppressFinalize(this);
}
}
}
Обратите внимание, что метод
Dispose()
был модифицирован для вызова метода
GC.SuppressFinalize()
, который информирует исполняющую среду о том, что вызывать деструктор при обработке данного объекта сборщиком мусора больше не обязательно, т.к. неуправляемые ресурсы уже освобождены посредством логики
Dispose()
.
Формализованный шаблон освобождения
Текущая реализация класса
MyResourceWrapper
работает довольно хорошо, но осталось еще несколько небольших недостатков. Во-первых, методы
Finalize()
и
Dispose()
должны освобождать те же самые неуправляемые ресурсы. Это может привести к появлению дублированного кода, что существенно усложнит сопровождение. В идеале следовало бы определить закрытый вспомогательный метод и вызывать его внутри указанных методов.
Во-вторых, желательно удостовериться в том, что метод
Finalize()
не пытается освободить любые управляемые объекты, когда такие действия должен делать метод
Dispose()
. В-третьих, имеет смысл также позаботиться о том, чтобы пользователь объекта мог безопасно вызывать метод
Dispose()
много раз без возникновения ошибки. В настоящий момент защита подобного рода в методе
Dispose()
отсутствует.
Для решения таких проектных задач в Microsoft определили формальный шаблон освобождения, который соблюдает баланс между надежностью, удобством сопровождения и производительностью. Вот окончательная версия класса
MyResourceWrapper
, в которой применяется официальный шаблон:
class MyResourceWrapper : IDisposable
{
// Используется для выяснения, вызывался ли метод Dispose().
private bool disposed = false;
public void Dispose()
{
// Вызвать вспомогательный метод.
// Указание true означает, что очистку
// запустил пользователь объекта.
CleanUp(true);
// Подавить финализацию.
GC.SuppressFinalize(this);
}