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

ЖАНРЫ

ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание

Троелсен Эндрю

Шрифт:

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

Создание объектов, предусматривающих освобождение ресурсов

Поскольку многие неуправляемые ресурсы являются столь "драгоценными", что их нужно освободить как можно быстрее, предлагается рассмотреть еще один подход, используемый для "уборки" ресурсов объекта. В качестве альтернативы переопределению Finalize класс может реализовать интерфейс IDisposable, который определяет единственный метод, имеющий имя Dispose.

public interface IDisposable {

 void Dispose;

}

Если

вы не имеете опыта программирования интерфейсов, то в главе 7 вы найдете все необходимые подробности. В сущности, интерфейс представляет собой набор абстрактных членов, которые могут поддерживаться классом или структурой. Если вы реализуете поддержку интерфейса IDisposable, то предполагается, что после завершения работы с объектом пользователь объекта должен вручную вызвать Dispose до того, как объектная ссылка "уйдет" из области видимости. При таком подходе ваши объекты смогут выполнить всю необходимую "уборку" неуправляемых ресурсов без размещения в очереди финализации и без ожидания сборщика мусора, запускающего программную логику финализации класса.

Замечание. Интерфейс IDisposable может поддерживаться и типами структуры, и типами класса (в отличие от переопределения Finalize, которое годится только для типов класса).

Ниже показан обновленный класс MyResourceWrapper, который теперь реализует IDisposable вместо переопределения System.Object.Finalize .

// Реализация IDisposable.

public class MyResourceWrapper: IDisposable {

 // Пользователь объекта должен вызвать этот метод

 // перед завершением работы с объектом.

 public void Dispose {

// Освобождение неуправляемых ресурсов.

// Освобождение других содержащихся объектов.

 }

}

Обратите внимание на то, что метод Dispose отвечает не только за освобождение неуправляемых ресурсов типа, но и за вызов Dispose для всех других содержащихся в его распоряжении объектов, предполагающих освобождение ресурсов. В отличие от Finalize, обращаться из метода Dispose к другим управляемым объектам вполне безопасно. Причина в том. что сборщик мусора не имеет никакого представления об интерфейсе IDisposable и никогда не вызывает Dispose. Поэтому, когда пользователь объекта вызывает указанный метод, объект все еще существует в управляемой динамической памяти и имеет доступ ко всем другим объектам, размещенным в динамической памяти. Логика вызова проста.

public class Program {

 static void Main {

MyResourceWrapper rw = new MyResourceWrapper;

rw.Dispose;

Console.ReadLine;

 }

}

Конечно, перед попыткой вызвать Dispose для объекта вы должны проверить, что соответствующий тип поддерживает интерфейс IDisposable. Обычно информацию об этом вы будете получать из документации .NET Framework 2.0 SDK, но это можно выяснить и программными средствами, используя ключевые слова is или as, применение которых обсуждалось в главе 4.

public class Program {

 static void Main {

MyResourceWrapper rw = new MyResourceWrapper;

if (rw is IDisposable) rw.Dispose;

Console.ReadLine;

 }

}

Этот

пример заставляет вспомнить еще одно правило работы с типами, предполагающими сборку мусора.

• Правило. Обязательно вызывайте Dispose для любого возданного вами объекта, поддерживающего IDisposable. Если разработчик класса решил реализовать поддержку метода Dispose, то типу, скорее всего, есть что "убирать".

Снова о ключевом слове using в C#

При обработке управляемых объектов, реализующих интерфейс IDisposable, вполне типичным будет использование методов структурированной обработки исключений (см. главу 6), чтобы гарантировать вызов метода Dispose даже при возникновении исключительных ситуаций в среде выполнения.

static void Main(string[] args) {

 MyResourceWrapper rw = new MyResourceWrapper;

 try {

// Использование членов rw.

 } finally {

// Dispose вызывается всегда, есть ошибки или нет.

rw.Dispose;

 }

}

Этот пример применения технологии "Безопасного программирования" прекрасен, но реальность такова, что лишь немногие разработчики готовы мириться с перспективой помещения каждого типа, предполагающего освобождение ресурсов, в рамки блока try/catch/finally только для того, чтобы гарантировать вызов метода Dispose. Поэтому для достижения того же результата в C# предусмотрен намного более удобный синтаксис, реализуемый с помощью ключевого слова using.

static void Main(string[] args) {

 // Dispose вызывается автоматически при выходе за пределы

 // области видимости using.

 using(MyResourceWrapper rw = new MyResourceWrapper) {

// Использование объекта rw.

 }

}

Если с помощью ildasm.exe взглянуть на CIL-код метода Main, то вы обнаружите, что синтаксис using на самом деле разворачивается в логику try/finally с ожидаемым вызовом Dispose.

.method private hidebysig static void Main(string [] args) cil managed {

 …

 .try {

 } // end try

 finally {

IL_0012: callvirt instance void SimpleFinalize.MyResourceWrapper::Dispose

 } // end handler

} // end of method Program::Main

Замечание. При попытке применить using к объекту, не реализующему интерфейс IDisposable, вы получите ошибку компиляции.

Этот синтаксис исключает необходимость применения "ручной укладки" объектов в рамки программной логики try/finally, но, к сожалению, ключевое слово using в C# является двусмысленным (оно используется для указания пространств имен и для вызова метода Dispose). Тем не менее, для типов .NET, предлагающих интерфейс IDisposable, синтаксическая конструкция using гарантирует автоматический вызов метода Dispose при выходе из соответствующего блока.

Исходный код. Проект SimpleDispose размещен в подкаталоге, соответствующем главе 5.

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