// Всегда вызывать Dispose, возникла ошибка или нет.
rw.Dispose;
}
Хотя это является хорошим примером защитного программирования, в действительности лишь немногих разработчиков привлекает перспектива помещения каждого освобождаемого типа внутрь блока
try/finally
, просто чтобы гарантировать вызов метода
Dispose
. Того же самого результата
можно достичь гораздо менее навязчивым способом, используя специальный фрагмент синтаксиса С#, который выглядит следующим образом:
Console.WriteLine("***** Fun with Dispose *****\n");
// Метод Dispose вызывается автоматически
// при выходе за пределы области действия using.
using(MyResourceWrapper rw = new MyResourceWrapper)
{
// Использовать объект rw.
}
Если вы просмотрите код CIL операторов верхнего уровня посредством
Несмотря на то что такой синтаксис устраняет необходимость вручную помещать освобождаемые объекты внутрь блоков
try/finally
, к сожалению, теперь ключевое слово
using
в C# имеет двойной смысл (импортирование пространств имен и вызов метода
Dispose
). Однако при работе с типами, которые поддерживают интерфейс
IDisposable
, такая синтаксическая конструкция будет гарантировать, что используемый объект автоматический вызовет свой метод
Dispose
по завершении блока
using
.
Кроме того, имейте в виду, что внутри
using
допускается объявлять несколько объектов одного и того же типа. Как и можно было ожидать, компилятор вставит код для вызова
Dispose
на каждом объявленном объекте:
// Использовать список с разделителями-запятыми для объявления
// нескольких объектов, подлежащих освобождению.
using(MyResourceWrapper rw = new MyResourceWrapper,
rw2 = new MyResourceWrapper)
{
// Работать с объектами rw и rw2.
}
Объявления using (нововведение в версии 8.0)
В версии C# 8.0 были добавлены объявления
using
. Объявление
using
представляет собой объявление переменной, предваренное ключевым
словом
using
. Функциональность объявления
using
будет такой же, как у синтаксиса, описанного в предыдущем разделе, за исключением явного блока кода, помещенного внутрь фигурных скобок (
{}
).
Добавьте к своему классу следующий метод:
private static void UsingDeclaration
{
// Эта переменная будет находиться в области видимости
// вплоть до конца метода.
using var rw = new MyResourceWrapper;
// Сделать что-нибудь.
Console.WriteLine("About to dispose.");
// В этой точке переменная освобождается.
}
Далее добавьте к своим операторам верхнего уровня показанный ниже вызов:
Console.WriteLine("***** Fun with Dispose *****\n");
...
Console.WriteLine("Demonstrate using declarations");
UsingDeclaration;
Console.ReadLine;
Если вы изучите новый метод с помощью
ildasm.exe
, то (вполне ожидаемо) обнаружите тот же код, что и ранее:
.method private hidebysig static
void UsingDeclaration cil managed
{
...
.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
, то объект со временем будет финализирован и получит шанс освободить внутренние ресурсы. Преимущество здесь в том, что внутренние неуправляемые ресурсы будут тем или иным способом освобождены.