Закрыть и утилизировать - куда звонить?


161

После прочтения темы Достаточно ли SqlCommand.Dispose? и закрытие и удаление службы WCF Мне интересно, для классов, таких как SqlConnection или одного из нескольких классов, унаследованных от класса Stream, имеет ли значение, если я закрываю Dispose, а не Close?

Ответы:


191

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

В соответствии с рекомендациями Microsoft, рекомендуется предоставлять 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, убедитесь, что вы делаете это безопасным для исключения способом (то есть в блоке catch или finally).


Когда вы говорите: «Вы должны провести необходимое исследование», что это за исследование? Единственный способ, которым я знаю, как сказать наверняка, - это через «Размышление», но в большинстве случаев это имеет обратную сторону, так как является «незаконным».
— Шторм

7
Я бы не сказал: conn.Close(); // Optionalэто не обязательно. Это излишне и ненужно. Вы дважды удаляете объект, и некоторые инструменты анализа кода помечают его как предупреждение.
— Metalogic

@Metalogic Я согласен, что излишне (и некрасиво) звонить в Close с правильным использованием. Однако, придирчивость: вызов Close не является «удалением» (тогда как Dispose подразумевает Close для SqlConnection). Сравните с тем using (var x = ..) { x.Dispose(); }, в каком случае это xдействительно "дважды".
— user2864740

11

Вам необходимо вызвать Dispose ()!

Dispose () предназначен для вызова разработчиком, сборщик мусора вызывает 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.
— Рене Кабис,

Я не уверен насчет других СУБД, но вы не можете сделать оба в 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/…
— Дэвид Кампс

@ DavidCumps Похоже, это изменилось за 4 года, с тех пор как я написал этот комментарий. Мои ссылки больше не действительны.
— Дина


6

Этот быстрый совет стал длинным ответом. Сожалею.

Как отметил Тайлер в своем хорошем ответе, вызов Dispose()- отличная практика программирования. Это связано с тем, что этот метод должен объединять все необходимое освобождение ресурсов, чтобы не было ненужных открытых ресурсов. Например, если вы записали какой-то текст в файл и не смогли закрыть файл (освободить ресурс), он останется открытым, и никто больше не сможет писать в него, пока GC не придет и не сделает то, что вы должны иметь. сделано.

Теперь, в некоторых случаях будут «финализировать» методы, более специфичные для класса, с которым вы имеете дело, например StreamWriter.Close(), который переопределяет TextWriter.Close(). На самом деле они обычно больше подходят для ситуации: Close()например, StreamWriter сбрасывает поток и базовый кодер перед Dispose()созданием объекта! Прохладно!

Однако, просматривая MSDN, вы обнаружите, что даже Microsoft иногда сбивает с толку множество приближенных и сторонников. На этой веб-странице , например, в некоторых примерах Close()вызывается перед неявным Dispose()(см. Оператор using, если вы не понимаете, почему он неявный), и в одном из них они не беспокоятся. С чего бы это? Я тоже был озадачен.

Причина, по которой я решил (и я подчеркиваю, что это оригинальное исследование, и я, безусловно, могу потерять репутацию, если я ошибаюсь), заключается в том, что он 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 безопасным для исключений способом. Это может быть хорошей идеей добавить обработку исключений в блок finally.
— Брэннон

Да, мои комментарии были специально для SqlClient. Дело в том, что вы должны понимать, какие классы вы используете. Всегда вызывать Dispose не обязательно правильный ответ.
— Брэннон

2

Наберите iDisposable и вызовите распоряжение на это. Это вызовет любой метод, сконфигурированный как реализующий «iDisposable.Dispose», независимо от того, как названа функция.


«Функция называется» «Утилизировать»: так что мы вернулись к первоначальному вопросу:}
— user2864740

Функция связана с IDisposable.Dispose, но это не значит, что это имя. Обратите внимание, что в vb.net можно привязать функцию к нескольким элементам интерфейса с именами, которые не обязательно должны быть связаны с именем функции.
— суперкат

В ролях так:using (myObj as IDisposable)
— Юша Алеауб

2

Обычно мы сталкиваемся с проблемой в Close (), Abort () и Dispose (), но позвольте мне рассказать вам разницу между ними.

1) ABORT: - Я не буду предлагать использовать это, потому что, когда вызывается abort, клиент удаляет соединение, не сообщая серверу, поэтому сервер будет ждать некоторое время (приблизительно 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неверна / вводит в заблуждение, потому что она не защищает все виды использования. Кроме того, я не уверен, как код может перейти в состояние «это соединение не открыто и должно быть закрыто».
— user2864740
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.