Следует ли использовать "Dispose" только для типов, содержащих неуправляемые ресурсы?
У меня недавно была беседа с коллегой о ценности Dispose
и типы, которые реализуют IDisposable
,
Я думаю, что есть смысл в реализации IDisposable
для типов, которые должны быть очищены как можно скорее, даже если нет неуправляемых ресурсов для очистки.
Мой коллега думает иначе; реализации IDisposable
если у вас нет неуправляемых ресурсов, нет необходимости, так как ваш тип в конечном итоге будет собирать мусор.
Мой аргумент состоял в том, что если у вас было соединение ADO.NET, которое вы хотели закрыть как можно скорее, тогда IDisposable
а также using new MyThingWithAConnection()
будет иметь смысл. Мой коллега ответил, что под прикрытием соединение ADO.NET является неуправляемым ресурсом. Мой ответ на его ответ состоял в том, что в конечном итоге все является неуправляемым ресурсом.
Мне известно о рекомендуемой схеме одноразового использования, когда вы освобождаете управляемые и неуправляемые ресурсы, если Dispose
вызывается, но только свободные неуправляемые ресурсы, если вызывается через финализатор / деструктор (и недавно опубликовал блог о том, как предупредить потребителей о ненадлежащем использовании ваших типов IDisposable)
Итак, мой вопрос: если у вас есть тип, который не содержит неуправляемые ресурсы, стоит ли его реализовать IDisposable
?
15 ответов
Существуют разные действительные варианты использования IDisposable
, Простой пример - хранение открытого файла, который необходимо закрыть в определенный момент, как только он вам больше не нужен. Конечно, вы могли бы предоставить метод Close
но имея это в Dispose
и используя шаблон, как using (var f = new MyFile(path)) { /*process it*/ }
будет более безопасным для исключения.
Более популярный пример будет содержать некоторые другие IDisposable
ресурсы, что обычно означает, что вам нужно предоставить свой собственный Dispose
чтобы распоряжаться ими тоже.
В общем, как только вы захотите что-то уничтожить, вам необходимо реализовать IDisposable
,
Разница между моим мнением и вашим заключается в том, что я реализую IDisposable
как только какой-то ресурс нуждается в детерминированном уничтожении / освобождении, не нужно как можно скорее. В этом случае не стоит полагаться на сборку мусора (вопреки утверждению вашего коллеги), потому что это происходит в непредсказуемый момент времени и фактически может вообще не произойти!
Тот факт, что любой ресурс не управляется под прикрытием, на самом деле ничего не значит: разработчик должен думать "когда и как правильно распоряжаться этим объектом", а не "как он работает под прикрытием". В любом случае, базовая реализация может меняться со временем.
Фактически, одно из основных отличий между C# и C++ заключается в отсутствии детерминированного уничтожения по умолчанию. IDisposable
позволяет сократить разрыв: вы можете заказать детерминированное уничтожение (хотя вы не можете гарантировать, что клиенты его вызывают; так же, как в C++, вы не можете быть уверены, что клиенты вызывают delete
на объекте).
Небольшое дополнение: в чем на самом деле разница между детерминистическим освобождением ресурсов и их скорейшим освобождением? На самом деле это разные (хотя и не полностью ортогональные) понятия.
Если ресурсы должны быть детерминированно освобождены, это означает, что в коде клиента должна быть возможность сказать "Теперь я хочу, чтобы этот ресурс был освобожден". Это может быть на самом деле не самый ранний из возможных моментов, когда ресурс может быть освобожден: объект, содержащий ресурс, может получить все необходимое от ресурса, поэтому потенциально он может уже освободить ресурс. С другой стороны, объект может сохранить ресурс (обычно неуправляемый) даже после Dispose
пробежал, очистив его только в финализаторе (если удержание ресурса слишком долгое время не создает проблем).
Итак, чтобы освободить ресурс как можно скорее, строго говоря, Dispose
не является необходимым: объект может освободить ресурс, как только он осознает, что ресурс больше не нужен. Dispose
однако служит полезным намеком на то, что сам объект больше не нужен, поэтому, возможно, ресурсы могут быть освобождены в этот момент, если это необходимо.
Еще одно необходимое дополнение: не только неуправляемые ресурсы требуют детерминированного освобождения! Это кажется одним из ключевых моментов различий во мнениях среди ответов на этот вопрос. Можно иметь чисто воображаемую конструкцию, которую, возможно, потребуется освободить детерминистически.
Примерами являются: право доступа к некоторой общей структуре (например, RW-блокировка), огромный кусок памяти (представьте, что вы управляете частью памяти программы вручную), лицензия на использование какой-либо другой программы (представьте, что вам не разрешено запускать более X копий какой-либо программы одновременно) и т. д. Здесь освобождаемый объект - это не неуправляемый ресурс, а право делать / использовать что-то, что является чисто внутренней конструкцией логики вашей программы.
Небольшое дополнение: вот небольшой список полезных примеров использования [ab] IDisposable
: http://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html.
Я думаю, что наиболее полезно думать о IDisposable
с точки зрения ответственности. Объект должен реализовать IDisposable
если он знает о чем-то, что нужно будет сделать между временем, в котором он больше не нужен, и концом вселенной (и желательно как можно скорее), и если это единственный объект, обладающий как информацией, так и стимулом для этого. Например, объект, открывающий файл, будет нести ответственность за закрытие файла. Если объект просто исчезнет, не закрывая файл, файл может не закрыться в течение любого разумного периода времени.
Важно отметить, что даже объекты, которые взаимодействуют только со 100% управляемыми объектами, могут делать вещи, которые необходимо очистить (и должны использовать IDisposable
). Например, IEnumerator
которое присоединяется к "измененному" событию коллекции, необходимо отсоединиться, когда оно больше не нужно. В противном случае, если перечислитель не использует какой-то сложный прием, перечислитель никогда не будет собирать мусор, пока коллекция находится в области видимости. Если коллекция перечисляется миллион раз, миллион обработчиков будет привязан к ее обработчику событий.
Обратите внимание, что иногда можно использовать финализаторы для очистки в тех случаях, когда по какой-либо причине объект был оставлен без Dispose
будучи вызванным первым. Иногда это работает хорошо; иногда это работает очень плохо. Например, хотя Microsoft.VisualBasic.Collection
использует финализатор для отделения перечислителей от "измененных" событий, пытаясь перечислить такой объект тысячи раз без вмешательства Dispose
или сборка мусора приведет к тому, что он станет очень медленным - на много порядков медленнее, чем производительность, которая могла бы возникнуть, если бы вы использовали Dispose
правильно.
Итак, мой вопрос: если у вас есть тип, который не содержит неуправляемых ресурсов, стоит ли реализовывать IDisposable?
Когда кто-то размещает интерфейс IDisposable на объекте, это говорит мне о том, что создатель намерен либо сделать что-то в этом методе, либо в будущем они могут это сделать. Я всегда называю dispose в этом случае, просто чтобы быть уверенным. Даже если он ничего не делает прямо сейчас, это может произойти в будущем, и это отстой, чтобы получить утечку памяти, потому что они обновили объект, и вы не вызывали Dispose, когда писали код в первый раз.
По правде говоря, это призыв к суждению. Вы не хотите чрезмерно реализовывать это, потому что в этот момент зачем вообще нужен сборщик мусора. Почему бы просто не утилизировать каждый объект вручную. Если есть вероятность, что вам нужно будет распоряжаться неуправляемыми ресурсами, это может быть неплохой идеей. Все зависит от того, что если вашими объектами являются только люди из вашей команды, вы всегда можете проконсультироваться с ними позже и сказать: "Эй, сейчас нужно использовать неуправляемый ресурс. Мы должны пройтись по коду и убедиться, что мы убрали ". Если вы публикуете это для других организаций, используйте другое. Нет простого способа сказать всем, кто мог бы реализовать этот объект: "Эй, тебе нужно быть уверенным, что это теперь утилизировано". Позвольте мне сказать вам, что есть несколько вещей, которые сводят людей с ума, чем обновление сторонней сборки, чтобы выяснить, что именно они изменили свой код и заставили ваше приложение избавиться от проблем с памятью.
Мой коллега ответил, что под прикрытием соединение ADO.NET является управляемым ресурсом. Мой ответ на его ответ состоял в том, что в конечном итоге все является неуправляемым ресурсом.
Он прав, это управляемый ресурс прямо сейчас. Будут ли они когда-нибудь изменить это? Кто знает, но это не мешало бы это назвать. Я не пытаюсь угадать, что делает команда ADO.NET, так что, если они ее вставляют, а она ничего не делает, это нормально. Я все еще буду называть это, потому что одна строка кода не повлияет на мою производительность.
Вы также столкнетесь с другим сценарием. Допустим, вы возвращаете соединение ADO.NET из метода. Вы не знаете, что соединение ADO является базовым объектом или производным типом. Вы не знаете, возникла ли необходимость в такой реализации IDisposable. Я всегда называю это несмотря ни на что, потому что отслеживание утечек памяти на рабочем сервере отстой, когда он выходит из строя каждые 4 часа.
Хотя на этот вопрос уже есть хорошие ответы, я просто хотел сделать что-то ясное.
Есть три случая для реализации IDisposable
:
- Вы используете неуправляемые ресурсы напрямую. Обычно это включает в себя получение
IntPrt
или некоторая другая форма дескриптора из вызова P/Invoke, которая должна быть освобождена другим вызовом P/Invoke - Вы используете другой
IDisposable
объекты и должны нести ответственность за их расположение - У вас есть другие потребности или использование для этого, в том числе удобство
using
блок.
Хотя я могу быть немного предвзятым, вы должны действительно прочитать (и показать своему коллеге) вики Stackru наIDisposable
,
Обратите внимание, что неуправляемые ресурсы могут также включать стандартные объекты CLR, например, хранящиеся в некоторых статических полях, и все они запускаются в безопасном режиме без какого-либо неуправляемого импорта.
Нет простого способа определить, реализует ли данный класс IDiposable
на самом деле нужно что-то чистить. Мое правило: всегда звонить Dispose
об объектах, которые я не очень хорошо знаю, например о какой-то сторонней библиотеке.
Dispose
следует использовать для любого ресурса с ограниченным сроком службы. Финализатор должен использоваться для любого неуправляемого ресурса. Любой неуправляемый ресурс должен иметь ограниченный срок службы, но существует множество управляемых ресурсов (таких как блокировки), которые также имеют ограниченный срок службы.
Нет, это не только для неуправляемых ресурсов.
Он предложен как базовый встроенный механизм очистки, называемый каркасом, который дает вам возможность очистить любой ресурс, который вы хотите, но лучше всего подходит естественное управление неуправляемыми ресурсами.
Если вы агрегируете IDisposable
Затем вы должны реализовать интерфейс, чтобы эти члены своевременно очищались. Как еще myConn.Dispose()
будет вызываться в примере подключения ADO.Net, который вы приводите?
Я не думаю, что правильно говорить, что в этом контексте все является неуправляемым ресурсом. Я также не согласен с вашим коллегой.
Вы правы. Соединения с управляемой базой данных, файлы, ключи реестра, сокеты и т. Д. Все держатся за неуправляемые объекты. Вот почему они реализуют IDisposable
, Если ваш тип владеет одноразовыми предметами, вы должны реализовать IDisposable
и избавиться от них в вашем Dispose
метод. В противном случае они могут остаться в живых до тех пор, пока мусор не будет собран, что приведет к заблокированным файлам и другим неожиданным действиям.
все в конечном итоге является неуправляемым ресурсом.
Не правда. Все, кроме памяти, используемой объектами CLR, которая управляется (выделяется и освобождается) только структурой.
Внедрение IDisposable
и звонит Dispose
для объекта, который не удерживается на каких-либо неуправляемых ресурсах (прямо или косвенно через зависимые объекты), бессмысленно. Это не делает освобождение этого объекта детерминированным, потому что вы не можете непосредственно освободить память CLR объекта самостоятельно, поскольку это всегда только GC
это делает это. Объект является ссылочным типом, потому что типы значений, когда используются непосредственно на уровне метода, выделяются / освобождаются операциями стека.
Теперь все утверждают, что правы в своих ответах. Позволь мне доказать свою. Согласно документации:
Метод Object.Finalize позволяет объекту попытаться освободить ресурсы и выполнить другие операции очистки, прежде чем он будет утилизирован сборщиком мусора.
Другими словами, память CLR объекта освобождается сразу после Object.Finalize()
называется. [примечание: возможно явно пропустить этот вызов при необходимости]
Вот одноразовый класс без неуправляемых ресурсов:
internal class Class1 : IDisposable
{
public Class1()
{
Console.WriteLine("Construct");
}
public void Dispose()
{
Console.WriteLine("Dispose");
}
~Class1()
{
Console.WriteLine("Destruct");
}
}
Обратите внимание, что деструктор неявно вызывает каждый Finalize
в цепочке наследования до Object.Finalize()
А вот и Main
метод консольного приложения:
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Class1 obj = new Class1();
obj.Dispose();
}
Console.ReadKey();
}
Если звонит Dispose
был ли способ освободить управляемый объект детерминированным способом, после каждого "удаления" сразу же следовал бы "уничтожить", верно? Смотрите сами, что происходит. Наиболее интересно запустить это приложение из окна командной строки.
Примечание: есть способ заставить GC
собрать все объекты, которые ожидают завершения в текущем домене приложения, но не для отдельного конкретного объекта. Тем не менее вам не нужно звонить Dispose
иметь объект в очереди завершения. Настоятельно не рекомендуется форсировать сбор данных, поскольку это, скорее всего, ухудшит общую производительность приложения.
РЕДАКТИРОВАТЬ
Есть одно исключение - государственное управление. Dispose
может обрабатывать изменения состояния, если ваш объект управляет внешним состоянием. Даже если состояние не является неуправляемым объектом, его очень удобно использовать как объект из-за особой обработки IDisposable
есть. Примером может служить контекст безопасности или контекст олицетворения.
using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate()))
{
// do something as SomeUser
}
// back to your user
Это не лучший пример, потому что WindowsImpersonationContext
использует системный дескриптор внутри, но вы получите картину.
Суть в том, что при реализации IDisposable
Вы должны иметь (или планируете иметь) что-то значимое, чтобы сделать в Dispose
метод. В противном случае это просто пустая трата времени. IDisposable
не меняет способ управления вашим объектом с помощью GC.
Нет необходимости в ресурсах (управляемых или неуправляемых). Часто, IDisposable
это просто удобный способ избавиться от try {..} finally {..}
Просто сравните:
Cursor savedCursor = Cursor.Current;
try {
Cursor.Current = Cursors.WaitCursor;
SomeLongOperation();
}
finally {
Cursor.Current = savedCursor;
}
с
using (new WaitCursor()) {
SomeLongOperation();
}
где WaitCursor
является IDisposable
быть подходящим для using
:
public sealed class WaitCursor: IDisposable {
private Cursor m_Saved;
public Boolean Disposed {
get;
private set;
}
public WaitCursor() {
Cursor m_Saved = Cursor.Current;
Cursor.Current = Cursors.WaitCursor;
}
public void Dispose() {
if (!Disposed) {
Disposed = true;
Cursor.Current = m_Saved;
}
}
}
Вы можете легко комбинировать такие классы:
using (new WaitCursor()) {
using (new RegisterServerLongOperation("My Long DB Operation")) {
SomeLongRdbmsOperation();
}
SomeLongOperation();
}
В одном из моих проектов у меня был класс с управляемыми потоками внутри, мы назовем их потоком A, потоком B и объектом IDisposable, назовем его C.
Используется для удаления C при выходе. B использовал C для сохранения исключений.
Мой класс должен был реализовать IDisposable и descrtuctor, чтобы гарантировать, что вещи расположены в правильном порядке. Да, сборщик мусора мог убирать мои вещи, но мой опыт заключался в том, что у меня было состояние гонки, если я не справился с уборкой своего класса.
Краткий ответ: Абсолютно НЕТ. Если ваш тип имеет членов, которые являются управляемыми или неуправляемыми, вы должны реализовать IDisposable.
Теперь подробности: я ответил на этот вопрос и предоставил гораздо более подробную информацию о внутренностях управления памятью и GC по вопросам здесь, на Stackru. Здесь только несколько:
- Это плохая практика, чтобы зависеть от.NET автоматического сборщика мусора?
- Что произойдет, если я не вызову Dispose на объекте пера?
- Утилизировать, когда это называется?
Что касается лучших практик по внедрению IDisposable, пожалуйста, обратитесь к моему сообщению в блоге:
Воплощать в жизнь IDisposable
если объект владеет какими-либо неуправляемыми объектами или какими-либо управляемыми одноразовыми объектами
Если объект использует неуправляемые ресурсы, он должен реализовать IDisposable
, Объект, которому принадлежит одноразовый объект, должен реализовать IDisposable
чтобы обеспечить освобождение базовых неуправляемых ресурсов. Если следовать правилу / соглашению, логично сделать вывод, что не выбрасывать управляемые одноразовые объекты равносильно освобождению неуправляемых ресурсов.
Ваш тип должен реализовывать IDisposable, если он ссылается на неуправляемые ресурсы или если он содержит ссылки на объекты, которые реализуют IDisposable.