dispose c что делает
Image. Dispose Метод
Определение
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Освобождает все ресурсы, используемые этим объектом Image.
Перегрузки
Освобождает все ресурсы, используемые этим объектом Image.
Освобождает неуправляемые ресурсы, используемые объектом Image, а при необходимости освобождает также управляемые ресурсы.
Комментарии
Dispose()
Освобождает все ресурсы, используемые этим объектом Image.
Реализации
Комментарии
Вызов Dispose метода позволяет перераспределить ресурсы, используемые этим методом, Image для других целей.
Вызовите метод Dispose по окончании использования класса Image. Метод Dispose оставляет класс Image в непригодном для использования состоянии. После вызова метода Dispose необходимо освободить все ссылки на объект Image, чтобы сборщик мусора мог освободить память, занимаемую объектом Image. Дополнительные сведения см. в разделе Очистка неуправляемых ресурсов и Реализация метода Dispose.
Применяется к
Dispose(Boolean)
Освобождает неуправляемые ресурсы, используемые объектом Image, а при необходимости освобождает также управляемые ресурсы.
Параметры
Значение true позволяет освободить как управляемые, так и неуправляемые ресурсы; значение false освобождает только неуправляемые ресурсы.
Комментарии
IDisposable. Dispose Метод
Определение
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Выполняет определяемые приложением задачи, связанные с удалением, высвобождением или сбросом неуправляемых ресурсов.
Примеры
В следующем примере показано, как можно реализовать Dispose метод.
Комментарии
Используйте этот метод, чтобы закрыть или освободить неуправляемые ресурсы, такие как файлы, потоки и дескрипторы, удерживаемые экземпляром класса, реализующего этот интерфейс. По соглашению этот метод используется для всех задач, связанных с освобождением ресурсов, удерживаемых объектом, или подготовки объекта к повторному использованию.
При реализации этого метода убедитесь, что все удерживаемые ресурсы освобождаются путем распространения вызова через иерархию вложений. Например, если объект A выделяет объект B, а объект б выделяет объект C, то Dispose Реализация метода должна вызываться Dispose в B, который должен в свою очередь вызывать Dispose на языке c.
Компилятор C++ поддерживает детерминированное удаление ресурсов и не допускает прямой реализации Dispose метода.
Если Dispose метод объекта вызывается более одного раза, объект должен игнорировать все вызовы после первого. Объект не должен вызывать исключение, если его Dispose метод вызывается несколько раз. Методы экземпляра, отличные от, Dispose могут вызывать исключение, ObjectDisposedException когда ресурсы уже удалены.
Поскольку Dispose метод должен вызываться явным образом, всегда существует опасность того, что неуправляемые ресурсы не будут освобождены, так как потребитель объекта не может вызвать его Dispose метод. Этого можно избежать двумя способами:
Реализуйте метод завершения для освобождения ресурсов, если Dispose не вызывается. По умолчанию сборщик мусора автоматически вызывает метод завершения объекта перед освобождением его памяти. Однако при Dispose вызове метода обычно не требуется, чтобы сборщик мусора вызывал метод завершения Dispose объекта. Чтобы предотвратить автоматическое завершение, Dispose реализации могут вызывать GC.SuppressFinalize метод.
[DotNetBook] Реализация IDisposable: правильное использование
Disposing (Disposable Design Principle)
IDisposable
Если спросить, что такое IDisposable, вы наверняка ответите что это
Для чего же создан интерфейс? Ведь если у нас есть умный Garbage Collector, который за нас чистит всю память, делает так, чтобы мы вообще не задумывались о том, как чистить память, то становится не совсем понятно, зачем ее вообще чистить. Однако есть нюансы.
Примечание
Глава, опубликованная на Хабре не обновляется и возможно, уже несколько устарела. А потому, прошу обратиться за более свежим текстом к оригиналу:
Вторым примером неуправляемых ресурсов являются примитивы синхронизации в многопоточных и мультипроцессных программах. Такие как мьютексы, семафоры. Или же массивы данных, которые передаются через p/invoke.
И ровно по этой причине есть два стандартных пути его вызова. Ведь, как правило, вы либо создаете экземпляр сущности, чтобы быстренько с ней поработать в рамках одного метода, либо в рамках времени жизни экземпляра своей сущности.
Вариации реализации IDisposable
Давайте пойдем в реализациях IDisposable от простого к сложному.
Первая и самая простая реализация, которая только может прийти в голову, — это просто взять и реализовать IDisposable:
Т.е. для начала мы создаем экземпляр некоторого ресурса, который должен быть освобожден и в методе Dispose() — освобождается.
Единственное, чего здесь нет и что делает реализацию не консистентной, это возможность дальнейшей работы с экземпляром класса после его разрушения методом Dispose() :
Давайте придумаем вариант с неуправляемым ресурсом.
Как же защититься от подобного? Для этих случаев мы обязаны реализовать финализатор объекта. Финализатор не случайно имеет именно такое название. Это вовсе не деструктор, как может показаться изначально из-за схожести объявления финализаторов в C# и деструкторов — в C++. Финализатор, в отличии от деструктора, вызовется *гарантированно*, тогда как деструктор может и не вызваться (ровно как и Dispose() ). Финализатор вызывается, когда запускается Garbage Collection (пока этого знания достаточно, но по факту все несколько сложнее), и предназначен для гарантированного освобождения захваченных ресурсов, если что-то пошло не так. И для случая освобождения неуправляемых ресурсов мы обязаны реализовывать финализатор. Также, повторюсь, из-за того, что финализатор вызывается при запуске GC, в общем случае вы понятия не имеете, когда это произойдет.
Давайте расширим наш код:
Мы усилили пример знаниями о процессе финализации и тем самым обезопасили приложение от потери информации о ресурсах, если что-то пошло не так и Dispose() вызван не будет. Дополнительно, мы сделали вызов GC.SuppressFinalize для того, чтобы отключить финализацию экземпляра типа, если для него был вызван Dispose(). Нам же не надо дважды освобождать один и тот же ресурс? Также это стоит сделать по другой причине: мы снимаем нагрузку с очереди на финализацию, ускоряя случайный участок кода, в параллели с которым будет в случайном будущем отрабатывать финализация.
Теперь давайте еще усилим наш пример:
Однако в этом коде существует очень серьезная проблема, которая не даст ему работать так, как задумали мы. Если мы повспоминаем, как отрабатывает процесс сборки мусора, то заметим одну деталь. При сборке мусора GC в первую очередь финализирует все, что напрямую унаследовано от Object, после чего принимается за те объекты, которые реализуют CriticalFinalizerObject. У нас же получается, что оба класса, которые мы спроектировали, наследуют Object: и это проблема. Мы понятия не имеем, в каком порядке мы уйдем на «последнюю милю». Тем не менее, более высокоуровневый объект может пытаться работать с объектом, который хранит неуправляемый ресурс — в своем финализаторе (хотя это уже звучит как плохая идея). Тут нам бы сильно пригодился порядок финализации. И для того чтобы его задать — мы должны унаследовать наш тип, инкапсулирующий unmanaged ресурс, от CriticalFinalizerObject.
SafeHandle / CriticalHandle / SafeBuffer / производные
У меня есть некоторое ощущение, что я для вас сейчас открою ящик Пандоры. Давайте поговорим про специальные типы: SafeHandle, CriticalHandle и их производные. И закончим уже, наконец, наш шаблон типа, предоставляющего доступ к unmanaged ресурсу. Но перед этим давайте попробуем перечислить все, что к нам _обычно_ идет из unmanaged мира:
Давайте напишем финальный вариант нашего класса, но теперь уже с последними знаниями о SafeHandlers:
Что его отличает? Зная, что если в DllImport методе в качестве возвращаемого значения установить любой SafeHandle-based тип, то Marshal его корректно создаст и проинициализирует, установив счетчик использований в 1, мы ставим тип SafeFileHandle в качестве возвращаемого для функции ядра CreateFile. Получив его, мы будем при вызове ReadFile и WriteFile использовать именно его (т.к. при вызове счетчик опять же увеличится, а при выходе — уменьшится, что даст нам гарантию существования handle на все время чтения и записи в файл). Тип этот спроектирован корректно, а это значит, что он гарантированно закроет файловый дескриптор. Даже когда процесс аварийно завершит свою работу. А это значит, что нам не надо реализовывать свой finalizer и все, что с ним связано. Наш тип значительно упрощается.
Многопоточность
Теперь поговорим про тонкий лед. В предыдущих частях рассказа об IDisposable мы проговорили одну очень важную концепцию, которая лежит не только в основе проектирования Disposable типов, но и в проектировании любого типа: концепция целостности объекта. Это значит, что в любой момент времени объект находится в строго определенном состоянии и любое действие над ним переводит его состояние в одно из заранее определенных — при проектировании типа этого объекта. Другими словами — никакое действие над объектом не должно иметь возможность перевести его состояние в то, которое не было определено. Из этого вытекает проблема в спроектированных ранее типах: они не потокобезопаны. Есть потенциальная возможность вызова публичных методов этих типов в то время, как идет разрушение объекта. Давайте решим эту проблему и решим, стоит ли вообще ее решать
Два уровня Disposable Design Principle
Что здесь не так и почему мы ранее в этой книге никогда так не писали? На самом деле шаблон хороший и без лишних слов охватывает все жизненные ситуации. Но его использование повсеместно, на мой взгляд, не является правилом хорошего тона: ведь реальных неуправляемых ресурсов мы в практике почти никогда не видим и в этом случае пол-шаблона работает в холостую. Мало того, он нарушает принцип разделения ответственности. Ведь он одновременно управляет и управляемыми ресурсами и неуправляемыми. На мой скромный взгляд, это совершенно не правильно. Давайте взглянем на несколько иной подход. *Disposable Design Principle*. Если коротко, то суть в следующем:
Disposing разделяется на два уровня классов:
Итоги
Плюсы
Минусы
Общие итоги
Ссылка на всю книгу
Programming stuff
Страницы
понедельник, 12 августа 2013 г.
О явном вызове метода Dispose
Но в его книгах (в особенности CLR via C#) есть ряд не вполне адекватных советов по дизайну наших с вами приложений. Так, Рихтер весьма категоричен при описании паттерна Dispose, который вполне валиден для разработчика библиотек, но сомнителен для разработчиков приложений.
И вот еще один весьма сомнительный совет по поводу необходимости явного вызова метода Dispose:
“Важно. В общем случае, я очень не рекомендую явно вызывать метод Dispose в вашем коде. Дело в том, что сборщик мусора CLR хорошо написан, и вы позволить ему делать свою работу. Сборщик мусора знает, когда объект становится недостижимым из кода приложения, и только тогда уничтожит его. Когда явно вызывается метод Dispose, то, по сути, это говорит, что приложению объект больше не нужен. Но во многих приложениях невозможно знать точно, что объект больше не используется.
Предположим, ваш код создает новый объект и вы передаете ссылку на этот объект в другой метод. Этот метод может сохранить ссылку на этот объект в некотором внутреннем поле (в корневой ссылке) и вызывающий метод никак не может узнать об этом. Конечно, вызывающий метод может вызвать Dispose, но потом, некоторый код может попытаться использовать этот объект и получит ObjectDisposeException. Я рекомендую явно вызывать метод Dispose только тогда, когда вы точно знаете, что в этом месте требуется очистка ресурсов (как в случае попытки удаления открытого файла).”
Джеффри Рихтера, “CLR via C#”, глава 21 “The Managed Heap and Garbage Collection”
Вообще, идея, чтобы каждый делал свою работу мне очень нравится;), и я согласен с советами, которые говорят о вреде в большинстве случаев явного вызова сборки мусора через GC.Collect. Но мне совсем не понятно, как можно говорить, чтобы «в общем случае» разработчики избегали вызова метода Dispose?
Давайте подумаем о том, в каких случаях мы используем disposable объекты и когда стоит вызывать метод Dispose, а когда нет.
1. Использование ресурсов в блоке using или идиома RAII (Resource Acquisition Is Initialization).
Напомню, что идиома RAII пришла из языка С++, в котором она является основой автоматического управления памятью и ресурсами. Ее идея в том, что в конструкторе объекта захватывается некоторый ресурс, а в деструкторе – он освобождается. При этом используется тот факт, что деструктор объекта будет вызван при выходе из области видимости автоматически, не зависимо от причина выхода из этой самой области видимости: выход по ключевому слову return или благодаря генерации исключения.
С моей точки зрения именно этот способ является наиболее распространенным и предпочтительным паттерном использования ресурсов. Ведь не зря, камрады из Редмонда так хвалятся тем, что благодаря новым асинхронным возможностям мы теперь можем пользоваться блоком using в наших асинхронных приложениях.
2. Использование ресурсов в качестве полей других объектов
Помимо использования в качестве локальной переменной, мы можем положить disposable объект в виде поля нашего класса. В этом случае наш класс тоже должен реализовать интерфейс IDisposable, который затем может использоваться в виде локальной переменной или в виде поля другого объекта.
В обоих этих случаях мы можем столкнуться с проблемой, поднятой Джеффри Рихтером и передать наш ресурс некоторому методу, который его сохранит. С другой стороны, подобное использование ресурсов противоречит всем канонам, ведь этот самый disposable объект является деталью реализации нашего метода или класса, и передача его куда-то во внешний мир в большинстве случаев является не лучшим решением.
С подобными рассуждениями мы можем прийти к выводу, что такие понятия как инварианты класса тоже не стоит использовать, ведь из конструктора вы можете вызвать некоторый метод и передать ему «себя» в недостроенном виде.
Тем более, что помимо приведенных случаев, есть еще один вариант использования паттерна Dispose.
3. Использование Disposable объектов не для управления ресурсами
Но в наших с вами дот нетах есть и исключения. Так, например, класс Task содержит финализатор, но не содержит неуправляемых ресурсов (да, таска может содержать управляемые ресурсы, но сейчас не об этом). В случае задач финализатор используется не для очистки ресурсов, а чтобы понять, является ли исключение текущей задачи «необработанным».
Помимо этого, есть примеры использования disposable объектов не для управления ресурсами напрямую, а для выполнения некоторых других операций.
3.1. Отписка от событий
Так, если ваш класс в конструкторе подписывается на событие долгоживущего объекта, то чтобы избежать утечки памяти нам нужно в некоторый момент времени от них отписаться. В этом случае финализатор нам никак не поможет, поскольку чтобы он вызвался нам нужно вначале отписаться от глобального события. В этом случае вполне валидным решением является использование для этих целей метода Dispose:
3.2. Освобождение блокировок или другие «постдействия»
Помимо отписки от событий или явного освобождения ресурсов метод Dispose и блок using может использовать для любых других произвольных действий. Например, мы можем использовать disposable оболочку для освобождения блокировок, аналогично тому, как это делается в С++:
Так, для класса ReaderWriterLockSlim мы можем сделать методы расширения, которые будут захватывать блокировку в конструкторе и освобождать ее в методе Dispose:
Да, некоторые товарищи, в том числе Эрик Липперт не одобряет использование блока using не для управления ресурсами, но иногда именно такой подход бывает довольно удобным.
Когда можно не вызывать Dispose?
А есть ли случаи, когда вызывать Dispose не обязательно? Да, есть, конечно.
Некоторые классы могут реализовывать интерфейс IDisposable, но при этом могут аллоцировать ресурсы лишь в редких случаях. Упомянутые вышеTask и Task реализуют IDisposable, но по словам Стивена Тауба (одного из авторов TPL) вызывать Dispose для них не нужно, поскольку ресурсы внутри объекта Task выделяются лишь в редких случаях.
Некоторые классы реализуют IDisposable, который достался от базового класса; таким примером является класс StringReader, который по своей природе не владеет ресурсами.
Бывают случаи, когда класс владеет некритическими ресурсами, при этом алгоритм владения является нечетким. Таким примером является класс ImageList из Windows Forms, который не очищает все содержащиеся в нем изображения.
В большинстве случаев и правда крайне не рекомендуется вызывать метод Dispose на объектах, созданных не вами. Также может быть опасным вызывать метод Dispose в многопоточных сценариях, поскольку это может привести к гонкам. Но в этом случае я бы скорее постарался ограничить многопоточное использование ресурса в одном классе, что позволит перед явной очисткой нашего ресурса дождаться завершения всех рабочих потоков.
Другими словами, случаи когда вы можете не вызывать метод Dispose и правда существуют, но я бы не назвал это явление таким уж распространенным. Дизайн класса говорит о намерениях проектировщика и реализация интерфейса IDisposable говорит клиентам класса, что объект поддерживает явное освобождение ресурсов. При этом не зная о о последствиях отсутствия вызова Dispose я бы рассматривал наиболее пессимистический вариант и старался бы освободить ресурсы явным образом.
Заключение
Если уж говорить о рекомендациях по вызову метода Dispose, то я бы озвучил совет так, как это сделал Джо Даффи, в замечательной книге “Framework Design Guidelines”:
“Если тип реализует интерфейс IDisposable и владение ресурсами очевидно (к чему в большинстве случаев стоит стремиться, прим. С.Т.), вы должны сделать все возможное для вызова метода Dispose после завершения использования объекта. Но если владение ресурсами неочевидно (поскольку объект используется во множестве мест или используется из нескольких потоков), то отсутствие вызова Dispose не причинит особого вреда. (Помимо ряда неприятных случаев, типа класса FileStream, когда отсутствие вызова метода Dispose может привести к непредсказуемым последствиям).”
Дополнительные ссылки
23 комментария:
Даффи: «Если тип реализует интерфейс IDisposable и владение ресурсами очевидно, вы должны сделать все возможное для вызова метода Dispose после завершения использования объекта»
Рихтер: «Я рекомендую явно вызывать метод Dispose только тогда, когда вы точно знаете, что в этом месте требуется очистка ресурсов»
То, что вы написали, просто показывает как можно использовать Dispose. Почему вы против слов Рихтера и за слова Даффи, и почему вы считает что ваши доводы против слов Рихтера и за доводы Даффи категорически непонятно.
Вадим, посмотрите на акценты в этих советах.
Джеффри пишет, что в подавляющем случае вызывать Dispose не нужно, но иногда можно.
Даффи пишет, чтобы вы сделали все для того, чтобы управлять ресурсами явно, но не переживали, если вас сделать этого не удается.
ИМХО это большая разница, поскольку опыт подсказывает, что зачастую код не расшаривает disposable объекты с другими объектами/потоками, что делает их явное освобождение предпочтительным.
Более того, в некоторых случаях (отписка от событий, освобождение блокировок/мьютексов, закрытие файлов/сокетов) отсутствие явного освобождения ресурсов приведет к непредсказуемому поведению приложения.
А чем мои доводы не устраивают? Я пишу о том, что Рихтер завязался на граничный случай применения disposable объектов. Вы с этим не согласны?
Позвольте небольшое дополнение по поводу 3.1.
Следует подчеркнуть: Dispose не просто что-то там чистит. Вызов Dispose подразумевает завершение работы с экземпляром объекта.
Если, допустим, Dispose нужен только для отписки от событий (и мы об этом знаем), то возможен следующий вариант:
1. Создаём экземпляр объекта.
2. Экземпляр подписывается на какие-то события.
3. Используем этот объект.
4. Вызываем Dispose.
5. Объект не выбрасываем, вместо этого возвращаемся к п.2.
Пример из жизни: в популярной библиотеке MVVM Light для этих целей существует специальный интерфейс ICleanup.
http://stackoverflow.com/questions/2963151/cleanup-vs-disposebool-in-mvvm-light
Всё это, конечно, не означает, что Dispose не должен уметь корректно отписываться.
Ммм. Видимо мы с вами воспринимаем текст через призму своего Я.
Я согласен с тем, что Dispose нужно вызывать только в том случае, если вы знаете что произойдет. Во всех остальных случая делать этого не стоит. Будь то закрытие транзакции, освобождение блокировок или просто очистка ресурсов.
Вадим, мне все зе кажется, что подход должен быть противоположным. Мы должны вызывать Dispose всегда и не вызывать его лишь тогда, когда точно знаем, что вреда от этого не будет.
В общем же случае, мы просто не знаем, будут или нет негативные последствия отсутствия вызова Dispose, поэтому мы должны приложить все усилия, чтобы вызвать этот метод.
В вашем примере я вызову Dispose если вы явно скажете что после использования вашего объекта желательно освободить ресурсы, либо в Dispose закрывается транзакция, либо еще что-то. Возможно что я предположу что закрыть транзакцию или освободить ресурсы удобно в Dispose, посмотрю что вы делаете в Dispose и только после этого задумаюсь о использовании Dispose.
Если мне не сказали что происходит в Dispose и что это удобно использовать,если объект не намекает на использование Dispose, я даже не посмотрю реализует ли объект IDisposable.
И все таки, Рихтер и Даффи говорят одно и тоже.
Даффи: «Если тип реализует интерфейс IDisposable и владение ресурсами очевидно, вы должны сделать все возможное для вызова метода Dispose после завершения использования объекта»
Рихтер: «Я рекомендую явно вызывать метод Dispose только тогда, когда вы точно знаете, что в этом месте требуется очистка ресурсов»
Т.е. если ты уверен, то вызывай, не уверен не вызывай. Если вы не вызовите Dispose, это не страшно, об этом говорят оба.
ДАффи: «Но если владение ресурсами неочевидно (поскольку объект используется во множестве мест или используется из нескольких потоков), то отсутствие вызова Dispose не причинит особого вреда.»
Так что это скорее вы приравниваете Dipose к деструктору, говоря что вызывать надо обязательно.
Вадим, Даффи и Рихтер говорят необ одном и том же. Даффи говорит об очевидности владения речурсами (мои первый и второй случай из статьи), а Рихтер говорит о том, что вы знаете, что эта очистка нужна. Это две совершенно разные вещи.
И я не приравниваю диспоз с деструктором, я говорю о том, что если я делаю класс диспозабл, то самим дизайном класса я говорю о важности вызова этого метода. Если класс реализует этот интерфейс, то это автоматом говорит всем его клиентам, что вызывать метод надо (пока документация или опыт использования не говорит обратного).
Не верно использовать Dispose не зная что там и зачем.
Яркий пример тому отложенная загрузка из какого либо источника. Источник реализует IDisposable. После этого из источника вы получаете объект, который загрузит данные только по требованию. Вызов Dispose у источника до того, как вы обратились к объекту с отложенной загрузкой есть ошибка. А источником может быть что угодно. И спрятать я могу источник за другими IDisposable объектами, которые в итоге вызовут Dispose у источника. И вызов Dispose у объекта «обертки» по вашему мне надо вызывать всегда, не зная что там происходит? И таких примеров может быть куча.
Вы ведь не можете быть уверены в том, что делает другой объект, только если вам явно не указали что он делает или если его написали не вы. И так как Dispose это такой же метод как и все, спрятать за ним можно все что угодно. Закрытие соединения, транзакции, разлочивание объекта. И делать все это вы должны только тогда, когда вам это нужно. Будь то даже работа с FileStream. Dispose нужен для удобства, что бы не забывать закрывать поток. Но вы это можете сделать и без Dispose, просто вызвав close. Т.е. обязательства вызывать Dispose нет. Более того, если бы я НЕ знал что там происходит, я бы не вызывал Dispose, т.к. название метода Dispose не дает мне ровным счетом никакого представления о том, что он делает. А вдруг он не закрывает FileStream, а уничтожает полностью файл? Ведь Dispose это не просто очистка памяти, это какое то действие, которое произойдет в момент вызова метода Dispose, или в момент выхода из оператора Using.
И опять таки про FileStream, т.к. этот объект будет владеть неуправляемыми ресурсами, то у него реализован финализатор. Т.е. если не вызывать Dispose, то «великого зла» не произойдет.
Для меня, реализация IDisposable, говорит лишь о том, что я могу этот объект использовать в using с удобством для себя(примеров может быть куча: транзакции, логирование, работа в рамках какого либо контекста, да даже профилирование). Но это не обязывает меня использовать Dispose. Это можно делать лишь тогда, когда мне это нужно и тогда когда я знаю что произойдет, когда меня об этом предупредили или объяснили что это обязательно.
И я не приравниваю диспоз с деструктором, я говорю о том, что если я делаю класс диспозабл, то самим дизайном класса я говорю о важности вызова этого метода.
Очень спорно. А если это класс логгер, или профайлер, или генеация HTML? И реализация IDisposable только для возможности использовать синтаксический сахар? И вызов Dispose «очень сильно» не обязателен?
Более того, ничего «криминального» не произойдет если ваш объект владеет управляемыми ресурсами. Другое дело неуправляемые ресурсы, но тут вы будете реализовывать финализатор, и опять таки Dispose можно вообще не реализовывать (хотя это будет не очень красиво).