Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

Подводя итоги, следует отметить, что хотя финализация объекта гарантирует ему возможность освобождения неуправляемых ресурсов, она все равно остается недетерминированной по своей природе, а из-за незаметной дополнительной обработки протекает значительно медленнее.

Построение освобождаемых объектов

Как вы уже видели, финализаторы могут использоваться для освобождения неуправляемых ресурсов при запуске сборщика мусора. Тем не менее, учитывая тот факт, что многие неуправляемые объекты являются "ценными элементами" (вроде низкоуровневых дескрипторов для файлов или подключений к базам данных), зачастую полезно их освобождать как можно раньше, не дожидаясь наступления сборки мусора. В качестве альтернативы переопределению метода

Finalize
класс может реализовать интерфейс
IDisposable
,
в котором определен единственный метод по имени
Dispose
:

public interface IDisposable

{

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 ;

Поделиться с друзьями: