Для встроенных классов Microsoft, которые наследуют IDisposable, должен ли я явно вызывать Dispose? - PullRequest
4 голосов
/ 12 января 2012

Что касается встроенных классов Microsoft, которые наследуют IDisposable, должен ли я явно вызывать Dispose для предотвращения утечек памяти?

Я понимаю, что лучше всего вызывать Dispose (или еще лучше использовать блок using), однако при программировании я обычно не всегда сразу понимаю, что класс наследуется от IDisposable.

Я также понимаю, что реализация IDisposable в Microsoft немного неэффективна, поэтому они создали статью, объясняющую правильное использование IDisposable .

Короче говоря, в каких случаях можно забыть позвонить в Dispose?

Ответы [ 7 ]

10 голосов
/ 12 января 2012

Есть несколько проблем в основном вопросе

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

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

Но вызовет ли Dispose утечку памяти?Возможно.Это очень зависит от того, что именно этот объект делает в своем методе Dispose.Много свободной памяти, некоторые отсоединяются от событий, другие освобождают дескрипторы и т. Д. Она может не пропускать память, но почти наверняка окажет негативное влияние на вашу программу

В каких случаях это нормальнозабыли позвонить в Dispose?

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

4 голосов
/ 12 января 2012

Это зависит от двух вещей:

  1. Что происходит в методе Dispose
  2. Вызывает ли финализатор Dispose

Функция Dispose
Утилита может выполнять несколько типов действий, например закрывать дескриптор ресурса (например, файлового потока), изменять состояние класса и освобождать другие компоненты, которые сам класс использует.
В случае освобождения ресурса (например,файл) есть функциональная разница между его явным вызовом и ожиданием вызова во время сборки мусора (при условии, что вызовы финализатора утилизируют).
В случае отсутствия изменения состояния и освобождения только компонентов, утечки памяти не будет, так какобъект будет освобожден GC позже.

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

Итог - в большинстве случаев лучше утилизировать объект явно, когда вы закончите сit.

Простой пример того, где лучше вызывать Dispose: если вы используете FileStream для записи некоторого содержимого и не разрешаете общий доступ, тогда файл блокируется процессомпока GC не получит объект.Файл может также не сбрасывать все содержимое в файл, поэтому, если процесс завершится сбоем в какой-то момент после завершения записи, это не гарантирует, что он будет фактически сохранен.

3 голосов
/ 12 января 2012

Это может быть безопасным, чтобы не вызывать Dispose, но проблема в том, что, когда дело обстоит так.

Хорошие 95% IEnumerator<T> реализаций имеют Dispose это безопасно игнорировать, но 5% - это не только 5%, которые вызовут ошибку, но и 5%, которые вызовут неприятную ошибку.Более того, код, который проходит через IEnumerator<T>, будет видеть 95% и 5% и не сможет динамически разделять их (возможно реализовать неуниверсальный IEnumerable без реализации IDisposable и насколько хорошо это оказалось, можно догадаться, когда MS решила заставить IEnumerator<T> наследовать от IDisposable!).

Из остальных, может быть, 3 или 4% времени это безопасно.Теперь.Вы не знаете, какие 3%, не взглянув на код, и даже тогда в контракте говорится, что вы должны вызывать его, поэтому разработчик может рассчитывать на то, что вы сделаете это, если выпустят новую версию там, где это важно.

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

По вопросу реализации IDisposable себя, избегайте шаблона в этом проклятом документе .

Я считаю этот паттерн анти-паттерном.Это хороший шаблон для реализации как IDisposable.Dispose, так и финализатора в классе, который содержит как управляемые, так и неуправляемые ресурсы.Однако в первую очередь держать как управляемые IDisposable, так и неуправляемые ресурсы - это плохая идея.

Вместо этого:

Если у вас есть неуправляемый ресурс, то не имеете никаких неуправляемых ресурсов, которые реализуютIDisposable.Теперь пути к кодам Dispose(true) и Dispose(false) одинаковы, поэтому в действительности они могут стать:

public class HasUnmanaged : IDisposable
{
  IntPtr unmanagedGoo;
  private void CleanUp()
  {
    if(unmanagedGoo != IntPtr.Zero)
    {
      SomeReleasingMethod(unmanagedGoo);
      unmanagedGoo = IntPtr.Zero;
    }
  }
  public void Dispose()
  {
    CleanUp();
    GC.SuppressFinalize(this);
  }
  ~HasUnmanaged()
  {
    CleanUp();
  }
}

Если у вас есть управляемые ресурсы, которые необходимо утилизировать, просто сделайте это:

public class HasUnmanaged : IDisposable
{
  IDisposable managedGoo;
  public void Dispose()
  {
    if(managedGoo != null)
      managedGoo.Dispose();
  }
}

Там нет никакого загадочного "избавления" от bool (как можно назвать Dispose и взять false за то, что называется disposing?) Не беспокойтесь о финализаторах в 99,99% случаев, когда вы не будетеони нужны (вторая модель встречается чаще, чем первая).Все хорошо.

Действительно нужно что-то, что имеет как управляемый, так и неуправляемый ресурс?Нет, на самом деле вы не заключаете неуправляемый ресурс в собственный класс, который работает с ним как дескриптор, а затем этот дескриптор соответствует первому шаблону выше, а основной класс - второму.

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

1 голос
/ 12 января 2012

Никогда не стоит забывать звонить Dispose (или, как вы говорите, лучше использовать using).

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

0 голосов
/ 13 января 2012

Microsoft (вероятно, не официально) говорит, что в некоторых случаях можно не вызывать Dispose.

Стивен Тауб из Microsoft пишет (о вызове Dispose on Task):

Короче говоря, , как это обычно бывает в .NET, агрессивно распоряжаться , если это просто и правильно сделать, основываясь на структуре вашего кода. Если вы начинаете делать странные движения, чтобы избавиться от (или в В случае задач используйте дополнительную синхронизацию, чтобы обеспечить ее безопасность. утилизировать, так как утилизировать можно только после завершения задачи), вероятно, лучше позаботиться о финализации. В конец, лучше измерить, измерить, измерить, чтобы увидеть, если вы на самом деле есть проблема, прежде чем вы изо всех сил, чтобы сделать код меньше в целях реализации функциональности очистки.

[жирный шрифт - мой]

Другой случай - базовые потоки

var inner = new FileStrem(...);
var outer = new StreamReader(inner, Encoding.GetEncoding(1252));
...
outer.Dispose();
inner.Dispose(); -- this will trigger a FxCop performance warning about calling Dispose twice.

(я отключил это правило)

0 голосов
/ 12 января 2012

Да, всегда звоните утилизировать. В явном или неявном виде (через использование). Взять, к примеру, класс Timer. Если вы явно не остановите таймер и не утилизируете его, он будет продолжать работать до тех пор, пока сборщик мусора не начнет собирать его. Это может вызвать сбои или непредвиденное поведение.

Всегда лучше убедиться, что Dispose вызывается, как только вы закончите с ним.

0 голосов
/ 12 января 2012

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

Другими словами, никогда не забывайте звонить Dispose().

...