Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
void Dispose();
}
При реализации интерфейса IDisposable предполагается, что когда пользователь объекта завершает с ним работу, он вручную вызывает метод Dispose() перед тем, как позволить объектной ссылке покинуть область действия. Таким способом объект может производить любую необходимую очистку неуправляемых ресурсов без помещения в очередь финализации и ожидания, пока сборщик мусора запустит логику финализации класса.
На заметку! Интерфейс IDisposable может быть реализован структурами не ref и классами (в отличие от переопределения метода Finalize(), что допускается только для классов), т.к. метод Dispose() вызывается пользователем объекта, а не сборщиком мусора. Освобождаемые структуры ref обсуждались в главе 4.
В целях иллюстрации применения интерфейса IDisposable создайте новый проект консольного приложения C# по имени SimpleDispose. Ниже приведен модифицированный класс MyResourceWrapper, который вместо переопределения метода System.Object.Finalize() теперь реализует интерфейс IDisposable:
using System;
namespace SimpleDispose
{
// Реализация интерфейса IDisposable.
class MyResourceWrapper : IDisposable
{
// После окончания работы с объектом пользователь.
// объекта должен вызывать этот метод
public void Dispose()
{
// Очистить неуправляемые ресурсы....
// Освободить другие освобождаемые объекты, содержащиеся внутри.
// Только для целей тестирования
Console.WriteLine("***** In Dispose! *****");
}
}
}
Обратите внимание, что метод Dispose() отвечает не только за освобождение неуправляемых ресурсов самого типа, но может также вызывать методы Dispose() для любых других освобождаемых объектов, которые содержатся внутри типа. В отличие от Finalize() в методе Dispose() вполне безопасно взаимодействовать с другими управляемыми объектами. Причина проста: сборщик мусора не имеет понятия об интерфейсе IDisposable, а потому никогда не будет вызывать метод Dispose(). Следовательно, когда пользователь объекта вызывает данный метод, объект все еще существует в управляемой куче и имеет доступ ко всем остальным находящимся там объектам. Логика вызова метода Dispose() прямолинейна:
using System;
using System.IO;
using SimpleDispose;
Console.WriteLine("***** Fun with Dispose *****n");
// Создать освобождаемый объект и вызвать метод Dispose().
// для освобождения любых внутренних ресурсов
MyResourceWrapper rw = new MyResourceWrapper();
rw.Dispose();
Console.ReadLine();
Конечно, перед попыткой вызова метода Dispose() на объекте понадобится проверить, поддерживает ли тип интерфейс IDisposable. Хотя всегда можно выяснить, какие типы в библиотеках базовых классов реализуют IDisposable, заглянув в документацию, программная проверка производится с помощью ключевого слова is или as (см. главу 6):
Console.WriteLine("***** Fun with Dispose *****n");
MyResourceWrapper rw = new MyResourceWrapper();
if (rw is IDisposable)
{
rw.Dispose();
}
Console.ReadLine();
Приведенный пример раскрывает очередное правило, касающееся управления памятью.
Правило. Неплохо вызывать метод Dispose() на любом создаваемом напрямую объекте, если он поддерживает интерфейс IDisposable. Предположение заключается в том, что когда проектировщик типа решил реализовать метод Dispose(), тогда тип должен выполнять какую-то очистку. Если вы забудете вызвать Dispose(), то память в конечном итоге будет очищена (так что можно не переживать), но это может занять больше времени, чем необходимо.
С предыдущим правилом связано одно предостережение. Несколько типов в библиотеках базовых классов, которые реализуют интерфейс IDisposable, предоставляют (кое в чем сбивающий с толку) псевдоним для метода Dispose() в попытке сделать имя метода очистки более естественным для определяющего его типа. В качестве примера можно взять класс System.IO.FileStream, который реализует интерфейс IDisposable (и потому поддерживает метод Dispose()), но также определяет следующий метод Close(), предназначенный для той же цели:
// Предполагается, что было импортировано пространство имен System.IO
static void DisposeFileStream()
{
FileStream fs = new FileStream("myFile.txt", FileMode.OpenOrCreate);
// Мягко выражаясь, сбивает с толку!
// Вызовы этих методов делают одно и то же!
fs.Close();
fs.Dispose();
}
В то время как "закрытие" (close) файла выглядит более естественным, чем его "освобождение" (dispose), подобное дублирование методов очистки может запутывать. При работе с типами, предлагающими псевдонимы, просто помните о том, что если тип реализует интерфейс IDisposable, то вызов метода Dispose() всегда является безопасным способом действия.
Повторное использование ключевого слова using в C#
Имея дело с управляемым объектом, который реализует интерфейс IDisposable, довольно часто приходится применять структурированную обработку исключений, гарантируя тем самым, что метод Dispose() типа будет вызываться даже в случае генерации исключения во время выполнения:
Console.WriteLine("***** Fun with Dispose *****n");
MyResourceWrapper rw = new MyResourceWrapper ();
try
{
// Использовать члены rw.
}
finally
{
// Всегда вызывать Dispose(), возникла ошибка или нет.
rw.Dispose();
}
Хотя это является хорошим примером защитного программирования, в действительности лишь немногих разработчиков привлекает перспектива помещения каждого освобождаемого типа внутрь блока try/finally, просто чтобы гарантировать вызов метода Dispose(). Того же самого результата можно достичь гораздо менее навязчивым способом, используя специальный фрагмент синтаксиса С#, который выглядит следующим образом:
Console.WriteLine("***** Fun with Dispose *****n");
// Метод Dispose() вызывается автоматически
// при выходе за пределы области действия using.
using(MyResourceWrapper rw = new MyResourceWrapper())
{
// Использовать объект rw.
}
Если вы просмотрите код CIL операторов верхнего уровня посредством ildasm.exe, то обнаружите, что синтаксис using на самом деле расширяется до логики try/finally с вполне ожидаемым вызовом Dispose():
.method private hidebysig static void
'<Main>$'(string[] args) cil managed
{
...
.try
{
} // end .try
finally
{
IL_0019: callvirt instance void [System.Runtime]System.IDisposable::Dispose()
} // end handler
} // end of method '<Program>$'::'<Main>$'
На заметку! Попытка применения using к объекту, который не реализует интерфейс IDisposable, приводит к ошибке на этапе компиляции.
Несмотря на то что такой синтаксис устраняет необходимость вручную помещать освобождаемые объекты внутрь блоков try/finally, к сожалению, теперь ключевое слово