При обработке управляемых объектов, реализующих интерфейс 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.
Создание типов, предусматривающих освобождение ресурсов и финализацию
К этому моменту мы с вами обсудили два различных подхода в построении классов, способных освобождать свои внутренние неуправляемые ресурсы. С одной стороны, можно переопределить System.Object.Finalize(), тогда вы будете уверены в том, что объект непременно освободит ресурсы при сборке мусора, без какого бы то ни было вмешательства пользователя. С другой стороны, можно реализовать IDisposable, что обеспечит пользователю возможность освободить ресурсы после завершения работы с объектом. Однако, если вызывающая сторона "забудет" вызвать Dispose(), неуправляемые ресурсы смогут оставаться в памяти неопределенно долгое время.
Вы можете догадываться, что есть возможность комбинировать оба эти подхода в одном определении класса. Такая комбинации позволит использовать преимущества обеих моделей. Если пользователь объекта
// Сложный контейнер ресурсов.
public class MyResourceWrapper: IDisposable {
// Сборщик мусора вызывает этот метод в том случае, когда
// пользователь объекта забывает вызвать Dispose().
~MyResourceWrapper() {
// Освобождение внутренних неуправляемых ресурсов.
// НЕ следует вызывать Dispose() для управляемых объектов.
}