Яка різниця між використанням IDisposable проти деструктора в C #?


101

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

Моє припущення полягає в тому, що якщо я реалізую IDispose на об'єкті, я можу явно його знищити на відміну від того, щоб чекати, коли сміттєзбірник це зробить. Це правильно?

Чи означає це, що я завжди повинен явно закликати розпоряджатися об'єктом? Назвіть кілька поширених прикладів цього?


5
Дійсно, вам слід зателефонувати за розпорядженням для кожного одноразового об’єкта. Це можна легко зробити за допомогою usingконструкції.
Люк Турайль

Ах, це має сенс. Мені завжди було цікаво, чому оператор "using" використовується для потоків файлів. Я знаю, що це мало відношення до області об’єкта, але я не ставлю це в контекст з інтерфейсом IDisposable.
Джордан Пармер

5
Важливо пам’ятати, що фіналізатор ніколи не повинен отримувати доступу до керованих членів класу, оскільки ці члени можуть більше не бути дійсними посиланнями.
Ден Брайант

Відповіді:


126

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

Тому IDisposableвикористовується для детермінованого очищення об'єктів, тобто зараз. Він не збирає пам'ять об'єкта (яка все ще належить до GC), але використовується, наприклад, для закриття файлів, підключень до бази даних тощо.

Про це є багато попередніх тем:

Нарешті, зауважте, що не рідкість IDisposableоб’єкт також має фіналізатор; в цьому випадку Dispose()зазвичай дзвонять GC.SuppressFinalize(this), тобто GC не запускає фіналізатор - він просто викидає пам'ять (набагато дешевше). Фіналізатор все ще працює, якщо ви забудете Dispose()об'єкт.


Дякую! Це має ідеальний сенс. Я дуже ціную чудовий відгук.
Джордан Пармер

27
Ще одне, що потрібно сказати. Не додайте фіналізатор до свого класу, якщо вам він справді не потрібен. Якщо ви додасте фіналізатор (деструктор), GC повинен викликати його (навіть порожній фіналізатор) і називати його, об'єкт завжди переживе збір сміття gen 1. Це утруднить і сповільнить ГК. Це те, що Марк каже зателефонувати SuppressFinalize у наведеному вище коді
Кевін Джонс

1
Отже, Finalize - це звільнення некерованих ресурсів. Але Dispose може бути використаний для випуску керованих та некерованих ресурсів?
Dark_Knight

2
@Dark так; тому що 6 рівнів вниз по ланцюгу управління може бути некерованим, який потребує оперативного очищення
Марк Гравелл

1
@KevinJones Об'єкти з фіналізатором гарантовано переживають gen 0, а не 1, правда? Я прочитав це у книзі під назвою .NET Performance.
Девід Клемффнер

25

Роль Finalize()методу полягає в тому, щоб об’єкт .NET міг очищати некеровані ресурси під час збирання сміття . Однак такі об’єкти, як підключення до бази даних або обробники файлів, повинні бути звільнені якнайшвидше, замість того, щоб покладатися на збирання сміття. Для цього вам слід реалізувати IDisposableінтерфейс та звільнити свої ресурси в Dispose()методі.


9

На MSDN є дуже хороший опис :

Основне використання цього інтерфейсу - це звільнення некерованих ресурсів . Збірник сміття автоматично звільняє пам'ять, виділену керованому об'єкту, коли цей об'єкт більше не використовується. Однак передбачити, коли відбуватиметься вивезення сміття, неможливо . Крім того, сміттєзбірник не має знань про некеровані ресурси, такі як ручки вікон або відкриті файли та потоки.

Використовуйте метод Dispose цього інтерфейсу, щоб явно звільнити некеровані ресурси спільно із сміттєзбірником. Споживач об'єкта може викликати цей метод , коли об'єкт більше не потрібен.


1
Основна слабкість цього опису полягає в тому, що МС наводить приклади некерованих ресурсів, але з того, що я бачив, ніколи насправді не визначає цей термін. Оскільки керовані об'єкти, як правило, використовуються лише в керованому коді, можна думати, що речі, які використовуються в некерованому коді, є некерованими ресурсами, але це не зовсім так. Багато керованого коду не використовує жодних ресурсів, а деякі види некерованих ресурсів, такі як події, існують лише у Всесвіті керованого коду.
суперкар

1
Якщо короткотривалий об’єкт передплачує подію від довгоживучого об'єкта (наприклад, він просить повідомити про будь-які зміни, що відбулися протягом життя короткотривалого об'єкта), таку подію слід вважати некерованим ресурсом, оскільки не вдалося скасування підписки на подію призведе до подовження терміну експлуатації короткочасного об'єкта до тривалості довготривалого об'єкта. Якщо багато тисяч або мільйони короткотривалих об’єктів підписалися на подію, але були залишені без підписки, це може спричинити витік пам'яті або процесора (оскільки час, необхідний для обробки кожної підписки, збільшиться).
supercat

1
Іншим сценарієм, що включає некеровані ресурси в керованому коді, буде розподіл об'єктів з пулів. Особливо, якщо код потрібно запускати в .NET Micro Framework (чий сміттєзбірник набагато менш ефективний, ніж той, що знаходиться на настільних машинах), код може бути корисним, наприклад, у масиві структур, кожна з яких може бути позначена "використаним" або "безкоштовно". Запит на розподіл повинен знайти структуру, яка в даний час позначена як "вільна", позначити її "використану" та повернути їй індекс; запит на випуск повинен позначати структуру як "вільну". Якщо запит на розподіл повертається, наприклад, 23, то ...
supercat

1
... якщо код ніколи не повідомляє власника масиву про те, що йому більше не потрібен елемент №23, цей слот масиву ніколи не буде використовуватися будь-яким іншим кодом. Такий ручний розподіл із слотів масиву не використовується дуже часто в коді робочого столу, оскільки GC досить ефективний, але в коді, який працює на Micro Framework, це може змінити величезну зміну.
supercat

8

Єдине, що має бути в деструкторі C #, це такий рядок:

Dispose(False);

Це воно. Нічого іншого ніколи не повинно бути в цьому методі.


3
Це шаблон дизайну, запропонований Microsoft у документації .NET, але не використовуйте його, коли ваш об'єкт не можна використовувати. msdn.microsoft.com/en-us/library/fs2xkftw%28v=vs.110%29.aspx
Zbyl

1
Я не можу придумати жодної причини запропонувати класу з фіналізатором, який також не має методу Dispose.
Джонатан Аллен

4

Ваше запитання щодо того, чи варто завжди телефонувати, Disposeчи ні, зазвичай - гаряча дискусія. Дивіться це блозі про цікаву перспективу від шанованих людей у ​​.NET-спільноті.

Особисто я вважаю позицію Джефрі Ріхтера, що закликає Dispose не є обов'язковими, надзвичайно слабка. Він наводить два приклади, щоб обгрунтувати свою думку.

У першому прикладі він каже, що Disposeзапуск елементів керування Windows Forms є стомлюючим та непотрібним у основних сценаріях. Однак він не зазначає, що Disposeнасправді викликається автоматично контейнерами управління в цих основних сценаріях.

У другому прикладі він зазначає, що розробник може помилково припустити, що екземпляр з нього IAsyncResult.WaitHandleслід агресивно розпоряджатися, не розуміючи, що властивість ліниво ініціалізує ручку очікування, що призводить до непотрібного покарання за виконання. Але проблема цього прикладу полягає в тому, що IAsyncResultсам по собі не дотримується власних опублікованих інструкцій Microsoft щодо поводження з IDisposableоб'єктами. Тобто, якщо клас містить посилання на IDisposableтип, то сам клас повинен реалізувати IDisposable. Якщо IAsyncResultдотримуватися цього правила, то його власнеDispose метод може прийняти рішення щодо того, хто з його складових потребує розпорядження.

Тож якщо у когось немає більш переконливого аргументу, я збираюся залишитися в таборі "завжди дзвонити", з розумінням того, що трапляються деякі випадкові випадки, які виникають з-за поганого дизайнерського вибору.


3

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

Зазвичай деструктор ніколи не повинен використовуватися. Це лише run .net хоче, щоб він запускався. Він працюватиме лише після циклу збирання сміття. Він фактично ніколи не може бути запущений протягом життєвого циклу вашої програми. З цієї причини ви ніколи не повинні ставити жодний код у деструктор, який слід «запустити». Ви також не можете покластися на будь-які існуючі об'єкти в класі, які існуватимуть під час його запуску (вони, можливо, вже були очищені, оскільки не гарантується порядок введення деструкторів).

IDisposible слід використовувати щоразу, коли у вас є об'єкт, який створює ресурси, які потребують очищення (тобто файли та графічні ручки). Насправді, багато хто стверджує, що все, що ви поклали в деструктор, повинно бути поставлено в ідентифікаційному порядку через причини, перелічені вище.

Більшість класів зателефонують у розпорядження, коли виконується фіналізатор, але це просто як безпечна охорона і на нього ніколи не слід покладатися. Вам слід явно розпоряджатися будь-чим, що реалізує IDisposable, коли ви закінчите з цим. Якщо ви реалізуєте IDisposable, вам слід зателефонувати утилізувати у фіналізаторі. Для прикладу див. Http://msdn.microsoft.com/en-us/library/system.idisposable.aspx .


Ні, сміттєзбірник ніколи не дзвонить утилізувати (). Це тільки викликає фіналізації.
Марк Гравелл

Виправлено це. Класи повинні викликати розпорядження у фіналізаторі, але цього не потрібно.
DaEagle

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.