В чем разница между использованием IDisposable и деструктора в C #?

101

Когда я могу реализовать IDispose в классе, а не деструктор? Я прочитал эту статью , но все еще не понимаю сути.

Я предполагаю, что если я использую IDispose для объекта, я могу явно «разрушить» его, а не ждать, пока сборщик мусора сделает это. Это верно?

Означает ли это, что я всегда должен явно вызывать Dispose для объекта? Каковы некоторые общие примеры этого?

Джордан Пармер
источник
5
Действительно, вы должны вызывать Dispose для каждого объекта Disposable. Вы можете легко сделать это с помощью usingконструкции.
Люк Турей,
Ах, в этом есть смысл. Я всегда задавался вопросом, почему для файловых потоков использовался оператор using. Я знаю, что это как-то связано с областью действия объекта, но я не помещал это в контекст с интерфейсом IDisposable.
Джордан Пармер,
5
Следует помнить один важный момент: финализатор никогда не должен обращаться к каким-либо управляемым членам класса, так как эти члены больше не могут быть действительными ссылками.
Дэн Брайант

Ответы:

126

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

Следовательно IDisposable, используется для детерминированной очистки объектов, то есть сейчас. Он не собирает память объекта (которая все еще принадлежит GC), но используется, например, для закрытия файлов, подключений к базе данных и т. Д.

По этому поводу много предыдущих тем:

Наконец, обратите внимание, что IDisposableобъект нередко также имеет финализатор; в этом случае Dispose()обычно вызывает GC.SuppressFinalize(this), что означает, что GC не запускает финализатор - он просто выбрасывает память (намного дешевле). Финализатор все равно работает, если вы забыли Dispose()объект.

Марк Гравелл
источник
Спасибо! В этом есть смысл. Я очень ценю отличный ответ.
Джордан Пармер,
27
Еще одна вещь, которую нужно сказать. Не добавляйте финализатор в свой класс, если он вам действительно не нужен. Если вы добавите финализатор (деструктор), сборщик мусора должен его вызвать (даже пустой финализатор), и чтобы вызвать его, объект всегда переживет сборку мусора первого поколения. Это будет препятствовать работе сборщика мусора и замедлять его. Это то, что Марк говорит вызывать SuppressFinalize в приведенном выше коде
Кевин Джонс,
1
Итак, Finalize - это освободить неуправляемые ресурсы. Но можно ли использовать Dispose для освобождения управляемых и неуправляемых ресурсов?
Dark_Knight 02
2
@ Темно да; потому что 6 уровней ниже по цепочке управления могут быть неуправляемым, который требует быстрой очистки
Марк Грейвелл
1
@KevinJones Объекты с финализатором гарантированно переживут поколение 0, а не 1, верно? Я прочитал об этом в книге под названием «Производительность .NET».
Дэвид Клемпфнер
25

Роль Finalize()метода состоит в том, чтобы гарантировать, что объект .NET может очищать неуправляемые ресурсы при сборке мусора . Однако такие объекты, как соединения с базой данных или обработчики файлов, должны быть освобождены как можно скорее, вместо того, чтобы полагаться на сборку мусора. Для этого вы должны реализовать IDisposableинтерфейс и освободить ресурсы в Dispose()методе.

Игал Табачник
источник
9

В MSDN есть очень хорошее описание :

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

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

Абатищев
источник
1
Одним из основных недостатков этого описания является то, что MS дает примеры неуправляемых ресурсов, но из того, что я видел, никогда не дает определения этого термина. Поскольку управляемые объекты обычно используются только в управляемом коде, можно подумать, что вещи, используемые в неуправляемом коде, являются неуправляемыми ресурсами, но это не совсем так. Большая часть неуправляемого кода не использует никаких ресурсов, а некоторые виды неуправляемых ресурсов, например события, существуют только в юниверсе управляемого кода.
supercat
1
Если короткоживущий объект подписывается на событие от долгоживущего объекта (например, он запрашивает уведомление о любых изменениях, которые происходят в течение времени жизни короткоживущего объекта), такое событие следует рассматривать как неуправляемый ресурс, поскольку сбой Отказаться от подписки событие приведет к увеличению времени жизни короткоживущего объекта до долгоживущего объекта. Если многие тысячи или миллионы недолговечных объектов подписались на событие, но были оставлены без отказа от подписки, это могло вызвать утечку памяти или ЦП (поскольку время, необходимое для обработки каждой подписки, увеличилось бы).
supercat
1
Другой сценарий, включающий неуправляемые ресурсы в управляемом коде, - это выделение объектов из пулов. Особенно, если код должен запускаться в .NET Micro Framework (чей сборщик мусора намного менее эффективен, чем на настольных компьютерах), может быть полезно, чтобы код имел, например, массив структур, каждая из которых может быть помечена как «использованная». или «бесплатно». Запрос на выделение должен найти структуру, которая сейчас помечена как «свободная», пометить ее как «использованную» и вернуть ей индекс; запрос на выпуск должен пометить структуру как «свободную». Если запрос на выделение возвращает, например, 23, то ...
supercat
1
... если код никогда не уведомляет владельца массива о том, что ему больше не нужен элемент # 23, этот слот массива никогда не будет использоваться каким-либо другим кодом. Такое ручное выделение слотов массива не очень часто используется в коде рабочего стола, поскольку сборщик мусора довольно эффективен, но в коде, выполняемом на Micro Framework, это может иметь огромное значение.
supercat
8

Единственное, что должно быть в деструкторе C #, - это такая строка:

Dispose(False);

Вот и все. В этом методе никогда не должно быть ничего другого.

Джонатан Аллен
источник
3
Это шаблон проектирования, предложенный Microsoft в документации .NET, но не используйте его, если ваш объект не является IDisposable. msdn.microsoft.com/en-us/library/fs2xkftw%28v=vs.110%29.aspx
Zbyl,
1
Я не могу придумать никаких причин для предложения класса с финализатором, у которого также нет метода Dispose.
Джонатан Аллен,
4

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

Лично я считаю, что позиция Джеффри Рихтера о том, что колл Disposeне является обязательным, невероятно слабая. В обоснование своего мнения он приводит два примера.

В первом примере он говорит, что вызов Disposeэлементов управления Windows Forms утомителен и не нужен в обычных сценариях. Однако он не упоминает, что Disposeфактически вызывается автоматически контейнерами управления в этих основных сценариях.

Во втором примере он заявляет, что разработчик может ошибочно предположить, что экземпляр from IAsyncResult.WaitHandleдолжен быть агрессивно удален, не осознавая, что свойство лениво инициализирует дескриптор ожидания, что приводит к ненужному снижению производительности. Но проблема с этим примером заключается в том, что IAsyncResultсам по себе не соответствует собственным опубликованным рекомендациям Microsoft по работе с IDisposableобъектами. То есть, если класс содержит ссылку на IDisposableтип, тогда должен быть реализован сам класс IDisposable. Если IAsyncResultследовать этому правилу, то его собственный Disposeметод мог бы принять решение относительно того, какие из составляющих его членов необходимо избавиться.

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

Брайан Гидеон
источник
3

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

Деструктор вообще не следует использовать. Это только запуск. Net хочет, чтобы он работал. Он будет запускаться только после цикла сборки мусора. Он может никогда не запуститься в течение жизненного цикла вашего приложения. По этой причине вы никогда не должны помещать в деструктор код, который «должен» быть запущен. Вы также не можете полагаться на то, что какие-либо существующие объекты в классе будут существовать при его запуске (возможно, они уже были очищены, поскольку порядок, в котором выполняются деструкторы, не гарантируется).

IDisposible следует использовать всякий раз, когда у вас есть объект, который создает ресурсы, требующие очистки (например, дескрипторы файлов и графики). Фактически, многие утверждают, что все, что вы помещаете в деструктор, должно быть помещено в IDisposable по причинам, перечисленным выше.

Большинство классов вызывают dispose при выполнении финализатора, но это просто надежная защита, и на него никогда нельзя полагаться. Вы должны явно удалить все, что реализует IDisposable, когда вы закончите с этим. Если вы действительно реализуете IDisposable, вам следует вызвать dispose в финализаторе. См. Пример на http://msdn.microsoft.com/en-us/library/system.idisposable.aspx .

DaEagle
источник
Нет, сборщик мусора никогда не вызывает Dispose (). Он вызывает только финализатор.
Марк Грейвелл
Исправлено. Классы должны вызывать dispose в финализаторе, но это не обязательно.
DaEagle