Закрити і розпоряджатися - куди дзвонити?


161

Прочитавши теми Чи достатньо SqlCommand.Розпоряджувати? і закриття та видалення послуги WCF Мені цікаво для таких класів, як SqlConnection або один із кількох класів, що успадковуються від класу Stream, чи має значення, якщо я закрию Dispose, а не Close?

Відповіді:


191

Я хочу прояснити цю ситуацію.

Відповідно до вказівок Майкрософт, це належна практика надати Closeметод, де це підходить. Ось цитування з керівних принципів дизайну Framework

Розглянемо спосіб надання Close(), крім того Dispose(), якщо близько, є стандартна термінологія в даній місцевості. Роблячи це, важливо, щоб ви зробили Closeреалізацію ідентичною Dispose...

У більшості випадків Closeі Disposeметоди рівнозначні. Основна відмінність між Closeі Disposeв разі SqlConnectionObjectстановить:

Додаток може телефонувати Closeне один раз. Не створюється виняток.

Якщо ви викликали Disposeметод, SqlConnectionстан об'єкта буде скинутий. Якщо ви спробуєте викликати будь-який метод на розміщеному SqlConnection об'єкті, ви отримаєте виняток.

Це сказав:

  • Якщо ви використовуєте об'єкт з'єднання один раз, використовуйте Dispose.
  • Якщо об'єкт з'єднання необхідно повторно використовувати, використовуйте Closeметод.

5
@Chris, документація для Close () говорить: "Потім він відпускає з'єднання до пулу з'єднань або закриває з'єднання, якщо об'єднання з'єднань вимкнено." Таким чином, Close () має бути достатнім, щоб запобігти переповненню пулу з'єднань.
— Девід Хаммонд

@DavidHammond: Ти маєш рацію. Я видаляю попередній коментар.
— NotMe

3
Чи вирішує .Dispose () також відпускати з'єднання назад у пул?
— oscilatingcretin

Це найкращий аргумент, який я читав з цього приводу так чи інакше за десятиліття. Відмінний момент.
— Майкл Еріксон

1
Так це працює так 1. con.Open() con.Close(); 2 con.Open(); // reuse 3. con.Dispose(); // use one time con.Open(); // error
— shaijut

24

Як завжди, відповідь така: це залежить. Різні класи реалізують IDisposableпо-різному, і вам належить зробити необхідні дослідження.

Що стосується SqlClient, рекомендована практика полягає в наступному:

using (SqlConnection conn = /* Create new instance using your favorite method */)
{
    conn.Open();
    using (SqlCommand command = /* Create new instance using your favorite method */)
    {
        // Do work
    }
    conn.Close(); // Optional
}

Вам слід дзвонити Dispose(або Close*) під час з'єднання! Ви НЕ чекати , поки збирач сміття , щоб очистити ваше з'єднання, це не буде підв'язати з'єднання в басейні до наступного циклу GC (по крайней мере). Якщо ви дзвоните Dispose, дзвонити не потрібно Close, а оскільки usingконструкція робить так просто керувати Disposeправильним, дійсно немає приводу дзвонити Close.

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

* якщо ви телефонуєте Close, переконайтеся, що це робиться безпечним для винятків способом (тобто у виловленні або остаточно заблокованому).


Коли ви говорите: "Вам належить зробити необхідні дослідження", що це за дослідження? Єдиний спосіб, який я знаю, як точно сказати, - це за допомогою рефлексії, але це має протилежне значення "незаконності" у більшості ситуацій.
— Буря

7
Я б не сказав: conn.Close(); // Optionalце не обов'язково. Це зайве і непотрібне. Ви розміщуєте об'єкт двічі, і це буде позначено як попередження деякими інструментами аналізу коду.
— Metalogic

@Metalogic Я погоджуюсь, що зайвим (і некрасивим) називати Закрити належним чином за допомогою звичок. Однак, виклик назовні: виклик Close не є "розпорядженням" (тоді як Dispose означає Close для SqlConnection). Порівняйте using (var x = ..) { x.Dispose(); }, у такому випадку xнасправді "утилізують двічі".
— користувач2864740

11

Вам НЕ потрібно телефонувати Dispose ()!

Dispose () призначений для виклику розробника, Garbage Collector викликає Finalize (). Якщо ви не зателефонуєте Dispose () на ваші об'єкти, будь-які некеровані ресурси, які вони використовували, не будуть утилізовані, поки сміттєзбірник не з’явиться і не зателефонує до їх завершення (і хто знає, коли це станеться).

Цей сценарій називається недетермінованою завершенням і є загальною пасткою для розробників .net. Якщо ви працюєте з об'єктами, які реалізують IDisposable, тоді зателефонуйте на них Dispose ()!

http://www.ondotnet.com/pub/a/oreilly/dotnet/news/programmingCsharp_0801.html?page=last

Хоча може бути багато випадків (наприклад, у SqlConnection), коли ви викликаєте Disponse () на якомусь об'єкті, і він просто викликає Close () під час з'єднання або закриває обробку файлу, майже завжди найкраща ставка для виклику Dispose ()! якщо ви не плануєте повторно використовувати об'єкт у найближчому майбутньому.


26
Цей коментар є абсолютно помилковим. Збирач сміття ніколи, ніколи не дзвонить Dispose.
— Стівен Клірі

3
Висновок: Вам слід зателефонувати, Dispose() якщо ви не використовуєте using()клас, який реалізує IDisposable. Якщо клас, який називається, реалізує IDisposable, і ви зафіксували його використання на сторінці всередині using(), тоді ви можете розпоряджатися Dispose()(каламбур призначений, тому знімайте мене). Використовуючи Close(), проте, рекомендується з чим - або , що явно використовує Open(), AFAIK.
— René Kåbis

Я не впевнений в інших СУБД, але ви НЕ можете робити обох у PostgreSql . Після Closeпідключення Postgres автоматично встановлює ідентифікатор з'єднання null. З цього Disposeмоменту не можна встановити ідентифікатор з'єднання sql, який уже встановлено null.
— ССД

10

Бо SqlConnectionз точки зору самого зв’язку вони рівнозначні. За словами Reflector, Dispose()дзвінки Close(), а також виконання декількох додаткових операцій із звільнення пам'яті - в основному шляхом встановлення членів рівним нулю.

Для Stream вони насправді є рівнозначними. Stream.Dispose()просто дзвонить Close ().


1
Ти впевнений? MSDN каже , що він успадкував відComponent якої , здається, не робити нічого , щоб спробувати викликуClose() . Я не бачу ніде, DBConnectionні того, SqlConnectionщо пов’язує жодне з цих повідомлень. Однак у нього є приватний, на DisposeMe()який ніде не посилаються .
— Діана

@Deanna це відмінено тут: github.com/dotnet/corefx/blob/…
— David Cumps

@DavidCumps Здається, це змінилося за 4 роки, відколи я написав цей коментар. Мої посилання більше не дійсні.
— Діанна


6

Ця швидка порада стала довгою відповіддю. Вибачте.

Як зазначив Тайлер у своїй гарній відповіді, дзвінки Dispose()- це чудова практика програмування. Це тому, що цей метод повинен "об'єднати" всі необхідні для звільнення ресурси, щоб не було зайвих відкритих ресурсів. Якщо ви, наприклад, записали текст у файл і не змогли закрити файл (вільний ресурс), він залишатиметься відкритим, і ніхто більше не зможе писати до нього, поки не відкриється GC і не зробить те, що у вас повинно бути зроблено.

Тепер, у деяких випадках, будуть "доопрацьовувати" методи, більш конкретні для класу, з яким ви маєте справу, як StreamWriter.Close(), наприклад , який перевизначає TextWriter.Close(). Насправді вони зазвичай більше підходять до ситуації: наприклад, StreamWriter, промиваючи Close()потік і базовий кодер, перед тим, Dispose()як об'єкт! Класно!

Однак переглянувши MSDN, ви побачите, що навіть Microsoft іноді плутають безліч закривальників і розпорядників. Наприклад, на цій веб-сторінці у деяких прикладах Close()називається перед неявним Dispose()(див. Використання оператора, якщо ви не розумієте, чому це неявно), а в одному, зокрема, вони не турбуються. Чому це було б? Я теж був здивований.

Причина, яку я зрозумів (і, наголошую, це оригінальне дослідження, і я, безумовно, можу втратити репутацію, якщо помиляюсь), - це те, що Close()може бути невдалим, створивши виняток, залишаючи ресурси відкритими, але Dispose(), безумовно, звільнить їх . Саме тому завжди повинен захищати в виклик (вибачте за каламбур).Dispose()Close()

MyResource r = new MyResource();

try {
  r.Write(new Whatever());

  r.Close()
finally {
  r.Dispose();
}

І так, я вважаю, що Microsoft скористався цим прикладом. Можливо, ця мітка часу ніколи не потрапляє у файл.

Завтра я виправляю старий код.

Редагувати: вибачте Браннон, я не можу коментувати вашу відповідь, але ви впевнені, що це гарна ідея зателефонувати Close()на finallyблок? Я думаю, виняток із цього може зіпсувати решту блоку, який, ймовірно, міститиме важливий код очищення.

Відповідь на запитання Браннона: чудово, просто не забудьте зателефонувати, Close()коли це дійсно потрібно (наприклад, при роботі з потоками - не знаєте багато про з'єднання SQL в .NET).


Насправді я ніколи не закликаю Close (), я просто дозволю Dispose () і конструкції "using" робити все правильно . Якщо ви не зателефонували до Dispose, вам потрібно дзвонити Close (Закрити) безпечним для винятків способом. Можливо, було б доречно додати остаточну обробку винятків.
— Браннон

Правильно, мої коментарі були спеціально для SqlClient. Справа в тому, що вам потрібно зрозуміти класи, які ви використовуєте. Завжди дзвонити у розпорядження - це не обов'язково правильна відповідь.
— Браннон

2

Наберіть на iDisposable і виклик розпоряджайтесь цим. Це дозволить викликати будь-який метод, налаштований як реалізація "iDisposable.Dispose", незалежно від того, яку функцію названо.


"Функція названа" "розпоряджатися": тому ми повертаємося до початкового питання:}
— user2864740

Функція пов'язана IDisposable.Dispose, але це не означає, що це ім'я. Зауважте, що в vb.net функція може бути пов'язана з декількома членами інтерфейсу з іменами, які не повинні бути пов’язані з назвою функції.
— supercat

У ролях так:using (myObj as IDisposable)
— Yousha Aleayoub

2

Зазвичай ми стикаємося з проблемою в Close (), Abort () та Dispose (), але дозвольте мені розповісти вам про їхню різницю.

1) АБОРТ: - Я не пропоную використовувати це, оскільки після виклику аборту клієнт видалить з'єднання, не повідомивши сервер, тому сервер буде чекати деяку кількість часу (приблизно 1 хв). Якщо у вас є масовий запит, ви не можете використовувати abort (), оскільки це може спричинити час для обмеженого пулу з'єднань.

2) Закрити: - Закрити - це дуже хороший спосіб закрити з'єднання, оскільки при закритті з'єднання він зателефонує серверу і підтвердить, що сервер також закриється.

Ось ще одна річ, яку потрібно подивитися. У деяких випадках, якщо помилка генерується, то це не гарний спосіб записати код, нарешті, що connection.close (), оскільки в цей час стан зв'язку буде виправданим.

3) Утилізуйте: - Це один тип закриття, але після закриття з'єднання ви не можете його знову відкривати.

Тож спробуйте так,

private void CloseConnection(Client client)
    {
        if (client != null && client.State == CommunicationState.Opened)
        {
            client.Close();
        }
        else
        {
            client.Abort();
        }
    }

Реєстрація client != nullневірна / оманлива, оскільки вона не захищає всіх звичаїв. Крім того, я не впевнений, як код може дійти до стану "це з'єднання не відкрито і його слід закрити".
— користувач2864740
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.