// Значение "false" указывает на то, что
// очистку инициировал сборщик мусора.
CleanDp(false);
}
}
Обратите внимание на то, что теперь MyResourceWrapper определяет приватный вспомогательный метод, с именем Cleanup(). Если для его аргумента указано true (истина), это значит, что сборку мусора инициировал пользователь объекта. И тогда мы должны освободить и управляемые, и неуправляемые ресурсы. Но если "уборка" инициирована сборщиком мусора, то при вызове CleanUp() следует указать false (ложь), чтобы внутренние объекты не освобождались (поскольку мы не можем гарантировать, что они все еще находятся в памяти). Наконец, перед выходом из CleanUp() член-переменная disposed логического типа устанавливается равной true, чтобы Dispose() можно было вызывать многократно без появления сообщений об ошибках.
Исходный код. Проект FinalizableDisposableClass размещен в подкаталоге, соответствующем главе 5.
На этом наше обсуждение того, как среда CLR управляет объектами с помощью сборки мусора, завершается. Остались нерассмотренными еще ряд вопросов, связанных с процессом сборки мусора (это, например, слабые ссылки и восстановление объектов), но вы теперь имеете достаточно знаний для того, чтобы продолжить исследование данной темы самостоятельно в удобное для вас время.
Целью этой главы было обсуждение процесса сборки мусора. Вы могли убедиться, что сборщик мусора начинает работу только тогда, когда становится невозможным выделить необходимый объем управляемой динамической памяти (или когда из памяти выгружается домен данного приложения). Вы можете быть уверены в том, что сборка мусора будет выполнена в оптимальном режиме с помощью соответствующего алгоритма Microsoft, использующего генерации объектов, вторичные потоки для финализации, объектов и области управляемой динамической памяти, предназначенные для размещения больших объектов.
В этой же главе объясняется, как с помощью типа класса System.GC взаимодействовать со сборщиком мусора. Это требуется при создании типов, предусматривающих финализацию или освобождение ресурсов. Типы, предусматривающие финализацию, представляют собой классы с переопределенным виртуальным методом System.Object.Finalize(), который (в будущем) должен обеспечить освобождение неуправляемых ресурсов. Объекты, предуcматриваюцие освобождение ресурсов, являются классами (или структурами), в которых реализуется интерфейс IDisposable. В рамках этого подхода пользователю объекта предлагается открытый метод, который должен быть вызван пользователем для выполнения внутренней ''уборки" сразу же, как только это потребуется. Наконец, вы узнали об "официальном" формализованном шаблоне освобождения ресурсов, в котором комбинируются оба указанных подхода.
ГЛАВА 6. Структурированная обработка исключений
Тема этой главы – устранение аномалий в среде выполнения вашего программного кода C# с помощью структурированной обработки исключений. Вы узнаете о ключевых словах C#, которые позволяют решать такие задачи (это ключевые слова try, catch, throw, finally), и выясните, в чем различие между исключениями системного уровня и уровня приложений. Данное обсуждение можно рассматривать, как введение в тему создания пользовательских исключений, а также как краткое описание средств отладки Visual Studio 2005, в основе которых, по сути, и лежит обработка исключений.
Ода ошибкам и исключениям
Вне зависимости от того, что говорит нам наша (часто преувеличенная) самооценка, абсолютно идеальных программистов не существует. Создание программного обеспечения является сложным делом и поэтому даже самые лучшие программы могут иметь проблемы. Иногда причиной проблем бывает несовершенный: программный код (например, выход за границы массива). В других случаях проблема возникает из-за неправильных данных, вводимых пользователем (например, ввод имени в поле телефонного номера), если возможность обработки таких данных не была предусмотрена в приложении. Независимо от причин возникающих проблем, результатом всегда оказывается непредусмотренное поведение приложения. Чтобы очертить рамки нашего обсуждения структурированной, обработки исключительных ситуаций, позвольте сформулировать определения трех терминов, часто используемых при изучении соответствующих аномалий.
• Программные ошибки. Это, попросту говоря, ошибки программиста. Например, при использовании языка C++ без управляемых расширений, если вызвать указатель NULL или забыть очистить выделенную память (в результате чего происходит ''утечка" памяти), то возникает программная ошибка.
• Пользовательские ошибки. В отличие от программных ошибок, пользовательские ошибки обычно возникают в результате действий объекта, использующего приложение, а не в результате действий разработчика. Например, действия пользователя, который вводит в текстовый блок строку "неправильного вида", могут привести к возникновению ошибки, если ваш программный код не в состоянии обработать такой ввод.
• Исключения. Исключения, или исключительные ситуации, обычно проявляются в виде аномалий среды выполнения, и очень трудно, если вообще возможно, полностью учесть их все в процессе программирования приложения. К возможным исключительным ситуациям относятся попытки соединиться с базой данных, которой больше не существует, открыть поврежденный файл, контактировать с компьютером, который выключен. Программист, как и конечные пользователи, имеют очень ограниченные возможности управления в подобных "исключительных обстоятельствах".
С учетом данных определений должно быть ясно, что структурированная обработка исключений в .NET предлагает подход, предназначенный для выявления исключительных ситуаций в среде выполнения. Однако и в случае программных и пользовательских ошибок, которые ускользнули от вашего внимания, среда CLR зачастую генерирует соответствующее исключение, идентифицирующее проблему. Для этого библиотеки базовых классов .NET определяют целый ряд исключений, таких как FormatException, IndexOutOfRangeException, FileNotFoundException, ArgumentOutOfRangeExсeption и т.д.
Перед тем как углубиться в детали, давайте определим формальную роль структурированной обработки исключений и выясним, чем такая обработка отличается от традиционного подхода в деле исправления ошибок.
Замечание. Чтобы сделать примеры программного кода, используемые в данной книге, как можно более конкретными, в них обрабатываются не асе возможные исключения, генерируемые соответствующими методами из библиотек базовых классов. Приемы, представленные в этой главе, вы можете использовать в своих реальных проектах по своему усмотрению.
Роль обработки исключений в .NET
До появления .NET обработка ошибок в операционной системе Windows представляла собой весьма запутанную смесь технологий. Многие программисты создавали свою собственную логику обработки ошибок, используемую в контексте приложения. Например, группа разработчиков могла определить набор числовых констант, представляющих известные сбойные ситуации, и использовать эти константы в качестве возвращаемых значений метода. Для примера взгляните на следующий фрагмент программного кода, созданного на языке C.
/* Типичный механизм учета ошибок в C. */
#define E_FILENOTFOUND 1000
int SomeFunction() {
// Предположим, что возникла ситуация, в результате
// которой возвращается следующее значение.
return E_FILENOTFOUND;
}
void Main() {
int retVal = SomeFunction();
if (retVal == E_FILE_NOTFOUND) printf("Не найден файл…");
}
Такой подход следует признать далеким от идеального, если учесть тот факт, что константа E_FILENOTFOUND является лишь числовым значением, а не информационным агентом, предлагающим решение возникшей проблемы. В идеале хотелось бы получать название ошибки, сообщение о ней и другую полезную информацию в одном пакете вполне определенного вида (и именно это предлагается в рамках структурированного подхода к обработке исключений).
В дополнение к приемам самого разработчика, Windows API предлагает сотни кодов ошибок, которые поставляются в виде #define, HRESULT, а также в виде многочисленных вариаций булевых значений (bool, BOOL, VARIANT_BOOL и т.д.). Многие разработчики программ на языке C++ (а также VB6) в рамках модели COM явно или неявно применяют ограниченный набор стандартных COM-интерфейсов (например, ISupportErrorInfo, IErrorInfo, ICreateErrorInfo), чтобы предоставить COM-клиенту информацию об ошибках.
Очевидной проблемой этой уже устаревшей схемы является отсутствие симметрии. Каждый из подходов более или менее укладывается в рамки своей конкретной технологии, конкретного языка и, возможно, даже в рамки конкретного проекта. Чтобы положить конец неуемному буйству разнообразия, платформа .NET предлагает стандартную технологию генерирования и выявления ошибок среды выполнения: структурированную обработку исключений – СОИ (Structured Exception Handling – SEH).