
- •С# и объектно-ориентированное программирование. Содержание
- •Разбор простой программы на с#.
- •Варианты метода Main ()
- •Формальное определение класса в с#
- •2.1Ссылки на самого себя
- •Построение цепочки вызовов конструкторов с использованием this.
- •Модификаторы доступа с#
- •Средства инкапсуляции с#
- •Инкапсуляция с использованием традиционных методов доступа и изменения
- •Инкапсуляция с использованием свойств .Net
- •Свойства, доступные только для чтения и только для записи
- •Статические свойства
- •Статические конструкторы
- •Наследование в с#
- •Работа с конструктором базового класса
- •Множественное наследование.
- •Ключевое слово sealed
- •Поддержка полиморфизма в с#
- •Ключевые слова virtual и override
- •Абстрактные классы
- •Полиморфный интерфейс
- •Сокрытие методов
- •Правила приведения к базовому и производному классу
- •Ключевое слово as
- •Ключевое слово is
- •Применение модели включения – делегирования.
- •Определение вложенных типов
- •Обработка исключений
- •Роль обработки исключений в .Net
- •Составляющие процесса обработки исключений в .Net
- •Генерация общего исключения
- •Перехват исключений
- •Создание специальных исключений, способ первый
- •Обработка нескольких исключений.
- •Блок finally
- •Замечания по работе с исключениями
- •Время жизни объектов
- •Базовые сведения о времени жизни объектов
- •Роль корневых элементов приложения
- •Поколения объектов
- •Параллельная сборка мусора в версиях .Net 1.0 - .Net 3.5
- •Фоновая сборка мусора в версии .Net 4.0
- •Создание финализируемых объектов
- •Описание процесса финализации
- •Создание высвобождаемых объектов
- •Повторное использование ключевого слова using в с#
- •Взаимодействие со сборщиком мусора
- •Принудительная активизация сборки мусора
- •Создание финализируемых и высвобождаемых типов
- •Формализованный шаблон очистки
Описание процесса финализации
Чтобы не делать лишнюю работу, следует всегда помнить, что задачей метода Finalize() является забота о том, чтобы объект .NET мог освобождать неуправляемые ресурсы во время сборки мусора. Следовательно, при создании типа, в котором никакие неуправляемые сущности не используются (так бывает чаще всего), от финализации оказывается мало толку. На самом деле, всегда, когда возможно, следует стараться проектировать типы так, чтобы в них не поддерживался метод Finalize() по той очень простой причине, что выполнение финализации отнимает время.
При размещении объекта в управляемой куче исполняющая среда автоматически определяет, поддерживается ли в нем какой-нибудь специальный метод Finalize(). Если да, тогда она помечает его как финализируемый (finalizable) и сохраняет указатель на него во внутренней очереди, называемой очередью финализации (finalizatlon queue). Эта очередь финализации представляет собой просматриваемую сборщиком мусора таблицу, где перечислены объекты, которые перед удалением из кучи должны быть обязательно финализированы.
Когда сборщик мусора определяет, что наступило время удалить объект из памяти, он проверяет каждую запись в очереди финализации и копирует объект из кучи в еще одну управляемую структуру, называемую таблицей объектов, доступных для финализации (finalization reachable table). После этого он создает отдельный поток для вызова метода Finalize() в отношении каждого из упоминаемых в этой таблице объектов при следующей сборке мусора. В результате получается, что для окончательной финализации объекта требуется как минимум два процесса сборки мусора.
Из всего вышесказанного следует, что хотя финализация объекта действительно позволяет гарантировать способность объекта освобождать неуправляемые ресурсы, она все равно остается недетерминированной по своей природе, и по причине дополнительной выполняемой незаметным образом обработки протекает гораздо медленнее.
Создание высвобождаемых объектов
Методы финализации могут применяться для освобождения неуправляемых ресурсов при активизации процесса сборки мусора. Однако многие неуправляемые объекты являются "ценными элементами" (например, низкоуровневые соединения с базой данных или файловые дескрипторы) и часто выгоднее освобождать их как можно раньше, еще до наступления момента сборки мусора. Поэтому вместо переопределения Finalize() в качестве альтернативного варианта также можно реализовать в классе интерфейс IDisposable, который имеет единственный метод по имени Dispose() :
public interface IDisposable
{
void Dispose ();
}
Интерфейс представляет собой коллекцию абстрактных членов, которые может поддерживать класс или структура. Когда действительно реализуется поддержка интерфейса IDisposable, то предполагается, что после завершения работы с объектом метод Dispose() должен вручную вызываться пользователем этого объекта, прежде чем объектной ссылке будет позволено покинуть область действия.
Благодаря этому объект может выполнять любую необходимую очистку неуправляемых ресурсов без попадания в очередь финализации и без ожидания того, когда сборщик мусора запустит содержащуюся в классе логику финализации.
На заметку! Интерфейс IDisposable может быть реализован как в классах, так и в структурах (в отличие от метода Finalize(), который допускается переопределять только в классах), потому что метод Dispose() вызывается пользователем объекта (а не сборщиком мусора).
Рассмотрим пример использования этого интерфейса:
// Реализация интерфейса IDisposable.
public class MyResourceWrapper: IDisposable
{
// После окончания работы с объектом пользователь
// объекта должен вызывать этот метод.
public void Dispose ()
{
// Освобождение неуправляемых ресурсов. . .
// Избавление от других содержащихся внутри
// и пригодных для очистки объектов.
}
}
Обратите внимание, что метод Dispose() отвечает не только за освобождение неуправляемых ресурсов типа, но и за вызов аналогичного метода в отношении любых других содержащихся в нем высвобождаемых объектов. В отличие от Finalize(), в нем вполне допустимо взаимодействовать с другими управляемыми объектами. Объясняется это очень просто: сборщик мусора не имеет понятия об интерфейсе IDisposable и потому никогда не будет вызывать метод Dispose(). Следовательно, при вызове данного метода пользователем объект будет все еще существовать в управляемой куче и иметь доступ ко всем остальным находящимся там объектам.
Логика вызова этого метода выглядит довольно просто:
class Program
{
static void Main(string [ ] args)
{
// Создание высвобождаемого объекта и вызов метода
// Dispose () для освобождения любых внутренних ресурсов.
MyResourceWrapper rw = new MyResourceWrapper();
rw.Dispose();
}
}
Разумеется, перед тем как пытаться вызывать метод Dispose() на объекте, нужно проверить, поддерживает ли соответствующий тип интерфейс IDisposable. И хотя в документации .NET Framework 4.0 SDK всегда доступна информация о том, какие типы в библиотеке базовых классов реализуют IDisposable, такую проверку удобнее выполнять программно с применением ключевого слова is или as.
class Program
{
static void Main(string[] args)
{
MyResourceWrapper rw = new MyResourceWrapper ();
if (rw is IDisposable)
rw.Dispose();
}
}
Этот пример раскрывает еще одно правило относительно работы с подвергаемыми сборке мусора типами.
Правило. Для любого создаваемого напрямую объекта, если он поддерживает интерфейс IDisposable, следует всегда вызывать метод Dispose(). Необходимо исходить из того, что в случае, если разработчик класса решил реализовать метод Dispose(), значит, классу надлежит выполнять какую-то очистку.
К приведенному выше правилу прилагается одно важное пояснение. Некоторые из типов, которые поставляются в библиотеках базовых классов и реализуют интерфейс IDisposable, предусматривают использование для метода Dispose () (несколько сбивающего с толку) псевдонима, чтобы заставить отвечающий за очистку метод звучать более естественно для типа, в котором он определяется. Для примера можно взять класс System.I0.FileStream, в котором реализуется интерфейс IDisposable (и, следовательно, поддерживается метод Dispose()), но при этом также определяется и метод Close (), каковой применяется для той же цели.
// Предполагается, что было импортировано пространство имен System.I0...
static void DisposeFileStream()
{
FileStream fs = new FileStream("myFile.txt", FileMode.OpenOrCreate);
// Мягко говоря, сбивает с толку!
// Вызовы этих методов делают одно и то же!
fs.Close();
fs.Dispose();
}
И хотя "закрытие" (close) файла действительно звучит более естественно, чем его "освобождение" (dispose), нельзя не согласиться с тем, что подобное дублирование отвечающих за одно и то же методов вносит путаницу. Поэтому при использовании этих нескольких типов, в которых применяются псевдонимы, просто помните о том, что если тип реализует интерфейс IDisposable, то вызов метода Dispose() всегда является правильным образом действия.