Должен ли "Dispose" использоваться только для типов, содержащих неуправляемые ресурсы?

Недавно я обсуждал с коллегой о значении Dispose и типах, которые реализуют IDisposable.

Я думаю, что существует значение при реализации IDisposable для типов, которые должны очищаться как можно скорее, , даже если нет неуправляемых ресурсов для очистки.

Мой коллега думает иначе; реализация IDisposable, если у вас нет неуправляемых ресурсов, не требуется, так как ваш тип в конечном итоге будет собираться с мусором.

Мой аргумент заключался в том, что если бы у вас было соединение ADO.NET, которое вы хотели закрыть как можно скорее, то реализация IDisposable и using new MyThingWithAConnection() имела бы смысл. Мой коллега ответил, что под обложками соединение ADO.NET является неуправляемым ресурсом. Мой ответ на его ответ состоял в том, что все в конечном итоге является неуправляемым ресурсом.

Я знаю рекомендуемый шаблон одноразового использования, где свободные управляемые и неуправляемые ресурсы, если Dispose называется, но свободные неуправляемые ресурсы, если вызваны через финализатор/деструктор (и ранее сообщал о том, как предупреждать потребителей о ненадлежащем использовании ваших IDisposable-типов)

Итак, мой вопрос: если у вас есть тип, который не содержит неуправляемые ресурсы, стоит ли реализовать IDisposable?

Ответ 1

Существуют разные допустимые применения для IDisposable. Простым примером является открытый файл, который вам нужно закрыть в определенный момент, как только он вам больше не понадобится. Конечно, вы могли бы предоставить метод Close, но наличие его в Dispose и использование шаблона, такого как using (var f = new MyFile(path)) { /*process it*/ }, было бы более безопасным.

В более популярном примере будут храниться некоторые другие ресурсы IDisposable, что обычно означает, что вам нужно предоставить свой собственный Dispose, чтобы избавиться от них.

В общем, как только вы хотите детерминировать уничтожение чего-либо, вам нужно реализовать IDisposable.

Различие между моим мнением и вашим заключается в том, что я реализую IDisposable, как только некоторый ресурс нуждается в детерминированном уничтожении/освобождении, не обязательно как можно скорее. Опираясь на сбор мусора, это не вариант в этом случае (вопреки заявлению коллеги), потому что это происходит в непредсказуемый момент времени, и на самом деле может не случиться вообще!

Тот факт, что какой-либо ресурс неуправляем под обложкой, на самом деле ничего не значит: разработчик должен подумать о терминах "когда и как правильно распоряжаться этим объектом", а не "как он работает под обложкой". Исходная реализация может измениться с течением времени.

На самом деле, одним из основных различий между С# и С++ является отсутствие детерминированного разрушения по умолчанию. IDisposable подходит для устранения разрыва: вы можете заказать детерминированное уничтожение (хотя вы не можете обеспечить, чтобы клиенты вызывали его, так же, как и на С++, вы не можете быть уверены, что клиенты звонят delete на объект).


Небольшое дополнение: какова на самом деле разница между детерминированным освобождением ресурсов и освобождением их как можно скорее? Собственно, это разные (хотя и не полностью ортогональные) понятия.

Если ресурсы должны быть детерминированы, это означает, что клиентский код должен иметь возможность сказать "Теперь я хочу, чтобы этот ресурс был освобожден". Это может быть на самом деле не самый ранний возможный момент, когда ресурс может быть освобожден: объект, содержащий ресурс, мог получить все, что ему нужно от ресурса, поэтому потенциально он может освободить ресурс уже. С другой стороны, объект может захотеть сохранить (обычно неуправляемый) ресурс даже после того, как пробежал объект Dispose, очистив его только в финализаторе (если держать ресурс слишком долго, это не создает никаких проблем).

Итак, для освобождения ресурса как можно скорее, строго говоря, Dispose не требуется: объект может освобождать ресурс, как только он осознает, что ресурс больше не нужен. Dispose однако служит полезным советом о том, что сам объект больше не нужен, поэтому, возможно, ресурсы могут быть освобождены в этом случае, если это необходимо.


Еще одно необходимое дополнение: это не только неуправляемые ресурсы, требующие детерминированного освобождения! Это, по-видимому, является одним из ключевых моментов разницы во мнениях между ответами на этот вопрос. Можно иметь чисто воображаемую конструкцию, которая может потребоваться освободиться детерминистически.

Примерами являются: право доступа к некоторой общей структуре (подумайте RW-lock), огромный фрагмент памяти (представьте, что вы управляете некоторыми из памяти программы вручную), лицензия на использование какой-либо другой программы (представьте, что вам не разрешено запускать больше, чем X-копии какой-либо программы одновременно) и т.д. Здесь объект, который должен быть освобожден, не является неуправляемым ресурсом, а правом делать/использовать что-то, что является чисто внутренней конструкцией для вашей программной логики.


Небольшое дополнение: вот небольшой список опрятных примеров [ab] с помощью IDisposable: http://www.introtorx.com/Content/v1.0.10621.0/03_LifetimeManagement.html#IDisposable.

Ответ 2

Я думаю, что наиболее полезно подумать о IDisposable в терминах обязанностей. Объект должен реализовывать IDisposable, если он знает о чем-то, что нужно будет сделать между временем, в котором он больше не нужен, и окончанием юниверса (и желательно как можно скорее), и если это единственный объект с информацией и стимул для этого. Например, объект, который открывает файл, будет обязан видеть, что файл закрыт. Если объект просто исчезнет, ​​не закрывая файл, файл может не закрыться в любой разумный таймфрейм.

Важно отметить, что даже объекты, которые взаимодействуют только со 100% управляемыми объектами, могут делать вещи, которые необходимо очистить (и должны использовать IDisposable). Например, IEnumerator, который присоединяется к коллективному "модифицированному" событию, должен будет отделить себя, когда он больше не нужен. В противном случае, если перечислитель не использует сложную обманку, перечислитель никогда не будет собирать мусор, пока коллекция находится в области видимости. Если сбор перечисляется в миллион раз, к его обработчику будет подключено миллион перечислителей.

Обратите внимание, что иногда иногда можно использовать финализаторы для очистки в тех случаях, когда по какой-либо причине объект забрасывается без вызова Dispose, который был вызван первым. Иногда это хорошо работает; someitmes это работает очень плохо. Например, даже если Microsoft.VisualBasic.Collection использует финализатор для отсоединения счетчиков от "измененных" событий, попытка тысячу раз перечислить такой объект без промежуточной коллекции Dispose или мусора приведет к ее очень медленному - многие заказы на величину медленнее, чем производительность, которая может возникнуть, если вы правильно использовали Dispose.

Ответ 3

Итак, мой вопрос: если у вас есть тип, который не содержит неуправляемые ресурсы, стоит ли реализовать IDisposable?

Когда кто-то помещает IDisposable-интерфейс в объект, это говорит мне, что создатель намеревается это делать либо что-то делать в этом методе, либо в будущем они могут намереваться. Я всегда вызываю dispose в этом случае, чтобы быть уверенным. Даже если он ничего не делает прямо сейчас, это может произойти в будущем, и это отпадает, чтобы получить утечку памяти, потому что они обновили объект, и вы не вызывали Dispose при написании кода в первый раз.

На самом деле это призыв к суду. Вы не хотите его реализовывать, потому что в этот момент вам вообще нужно иметь сборщик мусора. Почему бы просто не вручную уничтожить каждый объект. Если есть вероятность, что вам нужно будет распоряжаться неуправляемыми ресурсами, это может быть не плохой идеей. Все зависит от того, могут ли люди, использующие ваш объект, люди в вашей команде, вы можете всегда следить за ними позже и сказать: "Эй, теперь нужно использовать неуправляемый ресурс. Мы должны пройти через код и убедиться, что мы прибрали". Если вы публикуете это для других организаций, чтобы использовать это другое. Нет простого способа рассказать всем, кто мог бы реализовать этот объект: "Эй, вам нужно быть уверенным, что это теперь утилизировано". Позвольте мне сказать вам, что есть немного вещей, которые делают людей безумнее, чем модернизация сторонней сборки, чтобы узнать, что это те, кто изменил свой код и заставил ваше приложение сбежать от проблем с памятью.

Мой коллега ответил, что под обложками соединение ADO.NET является управляемый ресурс. Мой ответ на его ответ состоял в том, что все в конечном счете является неуправляемым ресурсом.

Он прав, это управляемый ресурс прямо сейчас. Смогут ли они когда-нибудь изменить его? Кто знает, но это не помешает назвать это. Я не пытаюсь делать догадки о том, что делает команда ADO.NET, поэтому, если они вставляют ее и ничего не делают, это прекрасно. Я все равно позвоню, потому что одна строка кода не повлияет на мою производительность.

Вы также запускаете другой сценарий. Скажем, вы возвращаете соединение ADO.NET из метода. Вы не знаете, что соединение ADO является базовым объектом или производным типом с летучей мыши. Вы не знаете, внезапно возникла ли эта реализация IDisposable. Я всегда называю это неважно, потому что отслеживание утечек памяти на производственном сервере отстойно, когда он разбивается каждые 4 часа.

Ответ 4

Пока есть хорошие ответы на это, я просто хотел сделать что-то явное.

Существует три случая реализации IDisposable:

  • Вы используете неуправляемые ресурсы напрямую. Обычно это включает в себя извлечение IntPrt или какой-либо другой формы дескриптора из вызова P/Invoke, который должен быть выпущен другим вызовом P/Invoke
  • Вы используете другие объекты IDisposable и должны нести ответственность за их расположение
  • У вас есть другая потребность или использование для него, включая удобство блока using.

Хотя я мог бы быть немного предвзятым, вы должны действительно прочитать (и показать своего коллегу) fooobar.com/questions/tagged/....

Ответ 5

Нет, это не только для неуправляемых ресурсов.

Он предлагался как встроенный механизм базовой очистки, называемый фреймворком, который позволяет вам очищать любой ресурс, который вам нужен, но он лучше всего подходит для управления неуправляемыми ресурсами.

Ответ 6

Обратите внимание, что неуправляемые ресурсы могут включать стандартные объекты CLR, например, хранящиеся в некоторых статических полях, все они работают в безопасном режиме без каких-либо неуправляемых импортов.

Нет простого способа узнать, действительно ли данный класс, реализующий IDiposable, должен что-то очистить. Мое правило состоит в том, чтобы всегда вызывать Dispose на объектах, которые я не слишком хорошо знаю, например, в какой-то сторонней библиотеке.

Ответ 7

Dispose следует использовать для любого ресурса с ограниченным сроком службы. Финализатор должен использоваться для любого неуправляемого ресурса. Любой неуправляемый ресурс должен иметь ограниченный срок службы, но есть много управляемых ресурсов (таких как блокировки), которые также имеют ограниченный срок службы.

Ответ 8

Если вы агрегируете IDisposable, тогда вы должны реализовать интерфейс, чтобы эти члены были своевременно очищены. Как еще myConn.Dispose() будет вызван вызов в примере соединения ADO.Net, который вы цитируете?

Я не думаю, что правильно сказать, что все это неуправляемый ресурс в этом контексте. Я также не согласен с вашим коллегой.

Ответ 9

Вы правы. Управляемые соединения с базами данных, файлы, ключи реестра, сокеты и т.д., Все они удерживаются на неуправляемых объектах. Вот почему они реализуют IDisposable. Если ваш тип владеет одноразовыми объектами, вы должны реализовать IDisposable и использовать их в своем методе Dispose. В противном случае они могут остаться в живых до тех пор, пока мусор не будет собран, что приведет к заблокированным файлам и другим непредвиденным поведением.

Ответ 10

все в конечном счете является неуправляемым ресурсом.

Не верно. Все, кроме памяти, используемой объектами CLR, которая управляется (выделяется и освобождается) только каркасом.

Реализация IDisposable и вызов Dispose для объекта, который не содержит никаких неуправляемых ресурсов (прямо или косвенно через зависимые объекты) бессмысленно. Он не освобождает этот объект детерминированный, потому что не может напрямую освобождать объектную память CLR на свой собственный, поскольку он всегда только GC что делает это. Объект является ссылочным типом, потому что типы значений, используемые непосредственно на уровне метода, выделяются/освобождаются операциями стека.

Теперь все утверждают, что они правы в своих ответах. Позвольте мне доказать мою. Согласно документации:

Object.Finalize Method позволяет объекту попытаться освободить ресурсы и выполнить другие операции очистки , прежде чем он будет восстановлен сборкой мусора.

Иными словами, объект 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 был способом освобождения объекта управляемого детерминированным способом, каждый "Dispose" сразу же следует за "Destruct", правильно? Посмотрите сами, что произойдет. Наиболее интересно запустить это приложение из окна командной строки.

Примечание. Существует способ заставить GC собирать все объекты, ожидающие завершения, в текущем домене приложения, но не для одного конкретного объекта. Тем не менее вам не нужно вызывать Dispose, чтобы иметь объект в очереди финализации. Настоятельно не рекомендуется принудительно собирать данные, поскольку это может повредить общей производительности приложений.

ИЗМЕНИТЬ

Есть одно исключение - управление состоянием. Dispose может обрабатывать изменение состояния, если ваш объект управляет внешним состоянием. Даже если состояние не является неуправляемым объектом, очень удобно использовать его как одно из-за специальной обработки IDisposable. Примером может быть контекст безопасности или контекст олицетворения.

using (WindowsImpersonationContext context = SomeUserIdentity.Impersonate()))
{
    // do something as SomeUser
}

// back to your user

Это не лучший пример, потому что WindowsImpersonationContext использует системный дескриптор внутри, но вы получаете изображение.

Нижняя строка заключается в том, что при реализации IDisposable вам нужно (или планировать) что-то значимое в методе Dispose. Иначе это просто пустая трата времени. IDisposable не изменяет управление вашим объектом GC.

Ответ 11

Ваш тип должен реализовывать IDisposable, если он ссылается на неуправляемые ресурсы или если он содержит ссылки на объекты, реализующие IDisposable.

Ответ 12

В одном из моих проектов у меня был класс с управляемыми потоками внутри него, мы будем называть их потоком A, потоком B и объектом IDisposable, мы назовем его C.

A используется для удаления C при выходе. B используется для использования C для сохранения исключений.

Мой класс должен был реализовать IDisposable и дескриптор для обеспечения того, чтобы вещи были утилизированы в правильном порядке. Да, GC мог бы очистить мои предметы, но мой опыт состоял в том, что было состояние гонки, если только мне не удалось очистить мой класс.

Ответ 13

Короткий ответ: Абсолютно НЕ. Если у вашего типа есть члены, которые управляются или неуправляемы, вы должны реализовать IDisposable.

Теперь подробности: Я ответил на этот вопрос и предоставил гораздо больше информации о внутренних функциях управления памятью и GC по вопросам здесь, в StackOverflow. Вот лишь несколько:

Что касается лучших практик реализации IDisposable, обратитесь к моему сообщению в блоге:

Как вы правильно реализуете шаблон IDisposable?

Ответ 14

Внедрить IDisposable, если объект владеет неуправляемыми объектами или любыми управляемыми одноразовыми объектами

Если объект использует неуправляемые ресурсы, он должен реализовать IDisposable. Объект, которому принадлежит одноразовый объект, должен реализовать IDisposable, чтобы освободить исходные неуправляемые ресурсы. Если следовать правилу/соглашению, поэтому логично заключить, что исключение управляемых одноразовых объектов не освобождает неуправляемые ресурсы.

Ответ 15

Не необходимые ресурсы вообще (управляемые или неуправляемые). Часто IDisposable - это просто удобный способ исключить combersome 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();
  }