Совершенно оправданно книга камрада Рихтера “CLR via C#” считается лучшей в своей роде. Более детального описания платформы .NET сложно найти, да и его книги по внутреннему устройству Windows тоже совершенно уникальны.
Но в его книгах (в особенности 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 или благодаря генерации исключения.
Многие другие языки программирования (и среды разработки) активно используют эту идиому. Так, в языке C# есть конструкция using, в C++/CLI – implicitly dereferenced variables (это когда переменная ссылочного типа используется без всяких галочек и процентов), в F# - use binding. Даже в Java, начиная с 7-й версии есть конструкция try-with-resources.
С моей точки зрения именно этот способ является наиболее распространенным и предпочтительным паттерном использования ресурсов. Ведь не зря, камрады из Редмонда так хвалятся тем, что благодаря новым асинхронным возможностям мы теперь можем пользоваться блоком using в наших асинхронных приложениях.
2. Использование ресурсов в качестве полей других объектов
Помимо использования в качестве локальной переменной, мы можем положить disposable объект в виде поля нашего класса. В этом случае наш класс тоже должен реализовать интерфейс IDisposable, который затем может использоваться в виде локальной переменной или в виде поля другого объекта.
В обоих этих случаях мы можем столкнуться с проблемой, поднятой Джеффри Рихтером и передать наш ресурс некоторому методу, который его сохранит. С другой стороны, подобное использование ресурсов противоречит всем канонам, ведь этот самый disposable объект является деталью реализации нашего метода или класса, и передача его куда-то во внешний мир в большинстве случаев является не лучшим решением.
С подобными рассуждениями мы можем прийти к выводу, что такие понятия как инварианты класса тоже не стоит использовать, ведь из конструктора вы можете вызвать некоторый метод и передать ему «себя» в недостроенном виде.
Тем более, что помимо приведенных случаев, есть еще один вариант использования паттерна Dispose.
3. Использование Disposable объектов не для управления ресурсами
В большинстве случаев на платформе .NET для управления ресурсами используются следующие конструкции: (1) классы, владеющие неуправляемыми ресурсами класс должен содержать финализатор (finalizer) и (2) класс, владеющий управляемыми или неуправляемыми ресурсами должен реализовывать интерфейс IDisposable (подробнее о разнице между управляемыми и неуправляемыми ресурсами можно почитать в статье “Dispose Pattern”).
Но в наших с вами дот нетах есть и исключения. Так, например, класс Task содержит финализатор, но не содержит неуправляемых ресурсов (да, таска может содержать управляемые ресурсы, но сейчас не об этом). В случае задач финализатор используется не для очистки ресурсов, а чтобы понять, является ли исключение текущей задачи «необработанным».
Помимо этого, есть примеры использования disposable объектов не для управления ресурсами напрямую, а для выполнения некоторых других операций.
3.1. Отписка от событий
Так, если ваш класс в конструкторе подписывается на событие долгоживущего объекта, то чтобы избежать утечки памяти нам нужно в некоторый момент времени от них отписаться. В этом случае финализатор нам никак не поможет, поскольку чтобы он вызвался нам нужно вначале отписаться от глобального события. В этом случае вполне валидным решением является использование для этих целей метода Dispose:
class CustomDisposable : IDisposable
{
public CustomDisposable()
{
AppDomain.CurrentDomain.UnhandledException += Handler;
}
public void Dispose()
{
AppDomain.CurrentDomain.UnhandledException -= Handler;
}
private void Handler(object sender, UnhandledExceptionEventArgs e)
{}
}
3.2. Освобождение блокировок или другие «постдействия»
Помимо отписки от событий или явного освобождения ресурсов метод Dispose и блок using может использовать для любых других произвольных действий. Например, мы можем использовать disposable оболочку для освобождения блокировок, аналогично тому, как это делается в С++:
Так, для класса ReaderWriterLockSlim мы можем сделать методы расширения, которые будут захватывать блокировку в конструкторе и освобождать ее в методе Dispose:
// Это не продакшн код, а лишь демонстрация возможностей!
static class ReaderWriterLockSlimEx
{
public static IDisposable UseRead(this ReaderWriterLockSlim rwLock)
{
rwLock.EnterReadLock();
return new ReadDisposeWrapper(rwLock);
}
class ReadDisposeWrapper : IDisposable
{
private readonly ReaderWriterLockSlim _rwLock;
public ReadDisposeWrapper(ReaderWriterLockSlim rwLock)
{
_rwLock = rwLock;
}
public void Dispose()
{
_rwLock.ExitReadLock();
}
}
}
Да, некоторые товарищи, в том числе Эрик Липперт не одобряет использование блока using не для управления ресурсами, но иногда именно такой подход бывает довольно удобным.
Когда можно не вызывать Dispose?
А есть ли случаи, когда вызывать Dispose не обязательно? Да, есть, конечно.
Некоторые классы могут реализовывать интерфейс IDisposable, но при этом могут аллоцировать ресурсы лишь в редких случаях. Упомянутые вышеTask и Task<T> реализуют IDisposable, но по словам Стивена Тауба (одного из авторов TPL) вызывать Dispose для них не нужно, поскольку ресурсы внутри объекта Task выделяются лишь в редких случаях.
Некоторые классы реализуют IDisposable, который достался от базового класса; таким примером является класс StringReader, который по своей природе не владеет ресурсами.
Бывают случаи, когда класс владеет некритическими ресурсами, при этом алгоритм владения является нечетким. Таким примером является класс ImageList из Windows Forms, который не очищает все содержащиеся в нем изображения.
В большинстве случаев и правда крайне не рекомендуется вызывать метод Dispose на объектах, созданных не вами. Также может быть опасным вызывать метод Dispose в многопоточных сценариях, поскольку это может привести к гонкам. Но в этом случае я бы скорее постарался ограничить многопоточное использование ресурса в одном классе, что позволит перед явной очисткой нашего ресурса дождаться завершения всех рабочих потоков.
Другими словами, случаи когда вы можете не вызывать метод Dispose и правда существуют, но я бы не назвал это явление таким уж распространенным. Дизайн класса говорит о намерениях проектировщика и реализация интерфейса IDisposable говорит клиентам класса, что объект поддерживает явное освобождение ресурсов. При этом не зная о о последствиях отсутствия вызова Dispose я бы рассматривал наиболее пессимистический вариант и старался бы освободить ресурсы явным образом.
Заключение
Если уж говорить о рекомендациях по вызову метода Dispose, то я бы озвучил совет так, как это сделал Джо Даффи, в замечательной книге “Framework Design Guidelines”:
“Если тип реализует интерфейс IDisposable и владение ресурсами очевидно (к чему в большинстве случаев стоит стремиться, прим. С.Т.), вы должны сделать все возможное для вызова метода Dispose после завершения использования объекта. Но если владение ресурсами неочевидно (поскольку объект используется во множестве мест или используется из нескольких потоков), то отсутствие вызова Dispose не причинит особого вреда. (Помимо ряда неприятных случаев, типа класса FileStream, когда отсутствие вызова метода Dispose может привести к непредсказуемым последствиям).”
Даффи: "Если тип реализует интерфейс 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 вреден только один - объект еще кем-то используется; но в большинстве случаев мы знаем об этом. Именно в этом мое мнение расходится с мнением Рихтера, который считает, что очень часто мы этого не знаем!)
Вт такой вопрос: я предоставил вам класс, реализующий интерфейс IDisposable; поведение этого метода - деталь реализации. Вы будете вызывать Dispose? Судя по вашим комментариям, вы этого делать не будете, я же считаю такой подход ошибочным, ведь это может привести к непредсказумым последствиям, включая утечку памяти и нсвоевременное освобождение ресурсов, которое приведет к трудноуловимым ошибкам.
В вашем примере я вызову Dispose если вы явно скажете что после использования вашего объекта желательно освободить ресурсы, либо в Dispose закрывается транзакция, либо еще что-то. Возможно что я предположу что закрыть транзакцию или освободить ресурсы удобно в Dispose, посмотрю что вы делаете в Dispose и только после этого задумаюсь о использовании Dispose.
ОтветитьУдалитьЕсли мне не сказали что происходит в Dispose и что это удобно использовать,если объект не намекает на использование Dispose, я даже не посмотрю реализует ли объект IDisposable.
И опять таки я соглашусь с Рихтером, это нужно использовать только тогда, когда это нужно вам, когда вы понимаете что происходит. Это не деструктор, который нужно вызывать для очистки памяти. Dispose это синтаксический сахар, который удобно использовать c Using. Это все равно что еще один метод, но это не деструктор. Деструктор надо вызывать всегда. Метод класса, нужно вызывать лишь тогда, когда это вам нужно, и когда вы понимаете что он делаете. Даффи же сводит Dispose к деструктору, что не верно. Замените метод Dispose на Close , CommitTransaction, EndTag и т.д. Получается что эти методы вы не будете стараться вызвать всегда, а лишь тогда когда вам это нужно. А ведь Dispose именно это и делает, просто теперь этот объект можно использовать в using, что есть синтаксический сахар и очень красиво, т.к. ограничивает область видимости и позволяет сборщику понимать что это больше не надо.
И все таки, Рихтер и Даффи говорят одно и тоже.
ОтветитьУдалитьДаффи: "Если тип реализует интерфейс IDisposable и владение ресурсами очевидно, вы должны сделать все возможное для вызова метода Dispose после завершения использования объекта"
Рихтер: "Я рекомендую явно вызывать метод Dispose только тогда, когда вы точно знаете, что в этом месте требуется очистка ресурсов"
Т.е. если ты уверен, то вызывай, не уверен не вызывай. Если вы не вызовите Dispose, это не страшно, об этом говорят оба.
ДАффи: "Но если владение ресурсами неочевидно (поскольку объект используется во множестве мест или используется из нескольких потоков), то отсутствие вызова Dispose не причинит особого вреда."
Так что это скорее вы приравниваете Dipose к деструктору, говоря что вызывать надо обязательно.
Вадим, Даффи и Рихтер говорят необ одном и том же. Даффи говорит об очевидности владения речурсами (мои первый и второй случай из статьи), а Рихтер говорит о том, что вы знаете, что эта очистка нужна. Это две совершенно разные вещи.
ОтветитьУдалитьИ я не приравниваю диспоз с деструктором, я говорю о том, что если я делаю класс диспозабл, то самим дизайном класса я говорю о важности вызова этого метода. Если класс реализует этот интерфейс, то это автоматом говорит всем его клиентам, что вызывать метод надо (пока документация или опыт использования не говорит обратного).
Рыть же исходники - это тоже не вариант, поскольку use cases класса определяются разработчиком класса, а не клиентом. И наиболее безопасный способ использования диспозабл объектов такой: считаем, что вызов метода Dispose жизненно необходим, пока не доказано обратное.
Вадим, существует принципиальная разница между деструктором в С++ и финализатором в .NET. Но разница эта заключается не в семантике этих двух зверей, а во времени вызова.
ОтветитьУдалитьИменно из-за недетерминированного вызова финализатора появился интерфейс IDisposable с дополнительными синтаксическими конструкциями типа блока using (кстати, именно эти конструкции являются синтаксическим сахаром, а не сам интерфейс IDisposable). При этом семантика (смысл, роль) деструкторов и метода Dispose - совпадает: они оба предназначены для разрушения инварианта объекта. Т.е. конструктор инвариант создает, затем мы пользуемся объектом, а деструктор или метод Dispose его разрушает.
В пользу этой мысли может служить язык C++\CLI в котором есть такое понятие как dereferenced variable (и dereferenced field) - это переменные ссылочных типов, объявленные без ^. И ведут себя они так: при выходе из области видимости вызывается метод Dispose.
Не верно использовать 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 можно вообще не реализовывать (хотя это будет не очень красиво).
При этом семантика (смысл, роль) деструкторов и метода Dispose - совпадает: они оба предназначены для разрушения инварианта объекта.
ОтветитьУдалитьЧто бы не писали в MSDN, семантика уже давно не совпадает. Возьмите Razor, возьмите логирование, возьмите MiniProfiler (http://miniprofiler.com/).
Вадим, почему вы считаете, что Dispose - это обычный метод? Разработчики .NET, например, говорят что именно Dispose метод является ключевым способом освобождения ресурсов, а финализатор должен использоваться в виде страховочного троса.
ОтветитьУдалитьВадим, тот факт, что кто-то использует что-то не по назначению еще ни о чем не говорит.
ОтветитьУдалитьВедь весь сыр-бор из-за чего: я отталкиваюсь от предположения, что вызывать Dispose нужно, как можно раньше и это должно быть нашим поведением по умолчанию. У вас мнение другое; я его ценю, хотя и не понимаю.
Я сам привел пару примеров, когда метод диспоз не разрушает состояние объекта, но тем не менее его вызов является необходимым. Если вы считает, что наиболее безопасно его не вызывать, что же, я возражать не буду.
Вадим, почему вы считаете, что Dispose - это обычный метод? Разработчики .NET, например, говорят что именно Dispose метод является ключевым способом освобождения ресурсов, а финализатор должен использоваться в виде страховочного троса.
ОтветитьУдалитьДа потому что это обычный метод по своей сути, с помощью которого реализуется интeрфейс IDisposable. :) Если я не вызову этот метод явно или используя using, его никто не вызовет. Поэтому если я использую неуправляемые ресурсы, то мне надо обязательно реализовывать финализатор и там либо дергать Dispose или освобождать ресурсы. Если объект реализует IDisposable, его можно использовать в using. Вот и все прелести.
ИМХО, минипрофайл построен на основе идиомы RAII, которая обеспечивает выполнение некоторого действия по выходу из области видимости. Да, ресурсов здесь нет (никаких), но ведь я приводил аналогичные примеры в пункте 3. Использование Disposable объектов не для управления ресурсами.
ОтветитьУдалитьТак в чем наше разногласие?
Вадим, деструктор в С++ - это тоже обычный метод, если я создам объект в куче, и затем его не удалю, то он не вызовется. На некотором низком уровне все является методом. Но у разных методов разный смысл.
ОтветитьУдалитьКонструктор ведь это тоже обычный метод, но именно там мы стараемся инициализировать инвариант класса. Но и он тоже не всегда вызывается, но это же не делает его бесполезным!
А спор о том, что вызывать Dispose надо только тогда, когда тебе это надо и ты знаешь что там происходит и зачем. Но не:
ОтветитьУдалитьчто вызов метода Dispose жизненно необходим, пока не доказано обратное.
Ок.
ОтветитьУдалитьУ камрада Скотта Мейерса есть одно любимое выражение: классом должно быть легко пользоваться правильно и сложно пользоваться неправильно.
И второе: дизайн класса должен передавать намерения разработчика.
Для чего кто-то делает Disposable-классы? Для того, чтобы сказать своим пользователям, что у него есть способ разрушить свое состояние, когда в нем отпадет необходимость. Правильно спроектированный класс будет реализовывать интерфейс Disposable только в том случае, когда его поведение согласуется с семантикой реализуемого интерфейса.
Если мы с вами сейчас говорим за вменяемый дизайн и реализацию, то в этом случае наличие интерфейса IDisposable должно говорить нам, что с этим классом нужно работать особым образом.
Разработчики классов и его пользователи могут не следовать этим советам и именно это делает нашу жизнь несколько сложнее. Какой подход по умолчанию выбрать - решать каждому из нас самостоятельно, но когда говорится о дизайне и общих советах, то тут следует отталкиваться от наиболее вероятных сценариев поведения.
Мой опыт говорит, что наличие вызова Dispose мне еще никогда не навредило, а вот благодаря отсутствию вызова, я сталкивался с проблемами. Этого же подхода придерживается товарищ Джо Даффи и авторы Framework Design Guidelines.
Сергей, я не говорю что использовать IDisposable не надо. Я говорю о том, что нужно понимать что делает Dispose. И вызывать лишь тогда, когда это нужно. Но это не должно быть обязательным. Мы все понимаем что надо обязательно закрывать FileStream. Но не обязательно вызывать Dispose. Понимаете о чем я хочу сказать? Это другая семантика, это другая задача. В .net нет объектов, которым обязательно надо вызывать Dispose. НЕ верно проектировать класс в надежде что Dispose вызовут. Более верно сделать логичное использование объекта, как в случае с FileStream (открыл/закрыл) и просто добавить возможность использовать объект в using, для того, что бы подсластить жизнь пользователей. И написать что close будет происходить в Dispose.
ОтветитьУдалитьБолее того т.к. в Dispose можно сделать очень многое и каждый может сделать все что угодно, нельзя вызывать Dispose бездумно. Не все проектируют верно, и вы не можете сказать точно что сделает Dispose и точно ли объекты которые используются в моей библиотеки не будут использовать диспозейбленный объект. Поэтому, я использую Dispose лишь тогда когда об этом говорит документация библиотеки (объекта), когда я знаю что произойдет и у меня есть на то причины.
Правильно спроектированный класс будет реализовывать интерфейс Disposable только в том случае, когда его поведение согласуется с семантикой реализуемого интерфейса.
ОтветитьУдалитьВ случае с FileStream, как мне кажется, не совсем логично предположить что в Dispose будет происходить Close.
Хотя прошлое мое утверждение спорно...
ОтветитьУдалить"Для чего кто-то делает Disposable-классы? Для того, чтобы сказать своим пользователям, что у него есть способ разрушить свое состояние, когда в нем отпадет необходимость. Правильно спроектированный класс будет реализовывать интерфейс Disposable только в том случае, когда его поведение согласуется с семантикой реализуемого интерфейса.
ОтветитьУдалитьЕсли мы с вами сейчас говорим за вменяемый дизайн и реализацию, то в этом случае наличие интерфейса IDisposable должно говорить нам, что с этим классом нужно работать особым образом.
Разработчики классов и его пользователи могут не следовать этим советам и именно это делает нашу жизнь несколько сложнее. Какой подход по умолчанию выбрать - решать каждому из нас самостоятельно, но когда говорится о дизайне и общих советах, то тут следует отталкиваться от наиболее вероятных сценариев поведения. " - именно так. По-моему этим всё сказано, не вижу больше причин для продолжения дискусси.
Все меняется если приложение использует ресурсоемкие неуправляемые объеты.
ОтветитьУдалитьЭксперименты показали, что GC достаточно милостиво относится к ним (даже если явно указывать давление на память и т.п.), а это достаточно неприятно если один такой объект может отжирать под 300метров и загружать под 25-30 процентов процессора.
GC поступает с ними как с любым объектом в старшем поколении - оставляет жить на долго не вызывая деструктора.
С одной стороны вьюмодели которые изначально без диспоуза с другой - где-то в глубине у них появляются такие вот монстры.
И тут приходится полностью забывать про автоматическое управление ресурсами и по всему дереву вьюмоделей внедрять диспоуз с обязательным использованием.
Хотя может есть иные - более остроумные способы, но я не нашел ничего.