#c# #dispose #sqlconnection
#c# #утилизировать #sqlconnection
Вопрос:
Я использую инструмент анализа кода Visual Studio, и одно из предупреждений, которое он мне выдает, гласит: «Не удаляйте объекты несколько раз: объект ‘conn’ может быть удален более одного раза в методе ‘CycleMessages.discriscan_reprint()’. Чтобы избежать создания системы.OjectDisposedEx вы не должны вызывать Dispose более одного раза для объекта. Строка:61
private void discernScan_Reprint()
{
try
{
DataTable dt = new DataTable();
SqlConnection conn = new SqlConnection("my constring");
using (conn)
{
SqlCommand cmd = new SqlCommand("usp_myproc", conn);
cmd.CommandType = CommandType.StoredProcedure;
using(cmd)
{
cmd.Parameters.Add(new SqlParameter("@param", param));
conn.Open();
SqlDataReader dr = cmd.ExecuteReader();
dt.Load(dr);
conn.Close(); // this is line 61
}
}
switch(dt.Rows.Count)
{
default:
throw new Exception("message");
case 1:
//do Stuff
case 0:
//do stuff
break;
}
}
catch (Exception ex) {throw;}
}
Я не распоряжаюсь conn (явно через conn.Dispose();) , Я просто закрываю его и разрешаю использовать инкапсуляцию для удаления объекта conn — я знаю, что могу разрешить его закрытие с помощью удаления, но почему он говорит, что я удаляю его дважды? Во всяком случае, он должен предупредить меня о том, что «вам не нужно прерывать соединения с объектами, которые будут удалены» или что-то в этом роде. Я что-то упускаю?
Правки: из ссылки ref при закрытии…
Метод Close откатывает все ожидающие транзакции. Затем он освобождает соединение с пулом соединений или закрывает соединение, если объединение в пул отключено.
и
Если SqlConnection выходит за рамки, он не будет закрыт. Следовательно, вы должны явно закрыть соединение, вызвав Close или Dispose . Close и Dispose функционально эквивалентны. Если для значения пула соединений Pooling установлено значение true или yes, базовое соединение возвращается обратно в пул соединений. С другой стороны, если для пула установлено значение false или no, базовое соединение с сервером закрывается.
Я знаю, что функционально Close() — это то же самое, что и dispose , но, насколько я понимаю, это не буквально. Когда я закрываю объект, он не удаляется. Он либо закрывается, либо возвращается в пул соединений, удаляя (опять же, насколько я понимаю) внутренние вызовы методов close() — поэтому, несмотря на избыточность, я все еще озадачен тем, почему он явно говорит, что он уже удален, когда это не так.
Комментарии:
1. В качестве дополнительного замечания, я думаю, вам лучше закрыть свое соединение в
finally
заявлении, чтобы быть уверенным, что оно действительно будет закрыто.2. Я удивлен, что инструмент также не указал на эту совершенно ненужную попытку / улов.
3. @Bartdude это плохой совет и совершенно ненужный;
using
сделает это сам по себе; если что: просто удалите.Close()
4. Оператор Using предоставляет объект в конце, который вызывает this. Close() в вашем SqlConnection. Проверьте это для получения дополнительной информации о инструкции using ( msdn.microsoft.com/en-us/library/yh598w02.aspx )
5. @MarcGravell > Виноват! Я упустил из виду, что
using
оператор действительно будет это делать…
Ответ №1:
Шаблон Dispose предполагает, что разработчики предоставляют синонимы для Dispose
, которые имеют смысл в контексте объекта. Одним из таких синонимов SqlConnection является Close()
:
Close и Dispose функционально эквивалентны.
Поскольку вы вызываете явно Close()
, а Dispose()
метод объекта вызывается при завершении using
инструкции соединения, вы фактически вызываете Dispose()
дважды.
Лучший подход заключается в том, чтобы просто позволить using
блоку обрабатывать это за вас, поскольку это гарантирует, что Dispose()
вызывается даже при возникновении исключения внутри using
блока. Он также устанавливает переменную в значение null, чтобы ее можно было использовать как можно скорее.
Правки в ответ на вопросы @alykin
В документации говорится, что методы Close()
и Dispose()
функционально эквивалентны, но @alykin определил сценарий, в котором они фактически не выполняют одно и то же. Если я правильно читаю ее комментарий, это работает примерно так:
Работает следующее:
SqlConnection conn = GetConnSomehow();
SqlCommand cmd = conn.CreateCommand();
// ...
conn.Open();
cmd.ExecuteSomething();
cmd.Close();
// ... time passes ...
conn.Open();
Следующее не:
SqlConnection conn = GetConnSomehow();
SqlCommand cmd = conn.CreateCommand();
using ( conn ) {
cmd.ExecuteSomething();
}
// ... time passes ...
// This won't compile, according to alykins.
conn.Open();
Это показывает, что объекты SqlConnection можно использовать повторно, по крайней мере, когда они были только Close()
разделены.
Вероятно, причина, по которой второй пример с блоком using не компилируется, заключается в том, что компилятор знает, что conn
было установлено значение null, когда using
блок заканчивается, поэтому он знает, что вы не можете вызывать методы по нулевой ссылке на объект.
Я все еще не уверен, что это показывает, что a Dispose()
на самом деле чем-то отличается от a Close()
, поскольку несоответствие возникает из-за семантики обнуления блока using. Было бы целесообразно проверить, можно ли повторно открыть SqlConnection после того, как оно Dispose()
‘d, но не обнулено. Даже если бы это было так, я бы не стал полагаться на такое поведение, поскольку оно противоречит собственным рекомендациям Microsoft, изложенным в документации по шаблону Dispose.
Кроме того, я бы не стал использовать первый блок, который не использует блок using — в случае возникновения исключения соединение может быть потеряно или, по крайней мере, оставаться открытым в течение недетерминированного периода времени, пока GC не увидит, что произошла утечка объекта, и не вызовет его финализатор.
Я бы не стал полагаться на какую-либо разницу в поведении между Close()
и Dispose()
— я бы рекомендовал не пытаться повторно открыть ранее закрытый объект SqlConnection. Позвольте пул-программе фактически поддерживать соединение, даже если вы закроете или утилизируете переданный вам объект SqlConnection.
Примечание об использовании инструкций.
Рассмотрим следующий блок кода:
IDisposable thing = GetThing();
using ( thing ) {
thing.DoWork();
}
Этот блок кода точно идентичен этому блоку:
IDisposable thing = GetThing();
try {
thing.DoWork();
}
finally {
thing.Dispose();
thing = null;
}
И следующий блок, по мнению Microsoft, их документации и инструментов анализа, считается двумя распоряжениями:
SqlConnection conn = GetConn();
using ( conn ) {
DoWork(conn);
conn.Close(); // code analysis tools count this as one Dispose().
} // implicit conn.Dispose() from the using block, so that's two.
Игнорируйте тот факт, что Close и Dispose точно не делают одно и то же. Они не хотят, чтобы вы полагались на это, и вы не должны этого делать, если поведение действительно исправлено.
Комментарии:
1. Чего я не понимаю, так это того, что удаление объекта полностью удаляет его, тогда как закрытие объекта возвращает его в пул соединений. Что возвращает меня к моему вопросу, если следовать логике, я закрываю его (возвращается в пул соединений), а затем удаляю его. Я знаю, что простое решение — удалить эту строку, но я пытаюсь понять смысл этого сообщения. Вы знаете, почему он выдал это сообщение?
2. @alykins —
Close()
иDispose()
оба делают точно то же самое, согласно документации. Они либо оба вернут его в пул соединений, если это объединенное соединение, либо оба сразу закроют его, если оно не является частью пула.3. Нет, они этого не делают… Попробуйте сделать это. продолжение. Открыть(); cmd. doSomeStuff(); продолжение. Закрыть(); // сделать кое-что; продолжение. Open(); и т.д… Теперь попробуйте то же самое, но для первого оператора используйте (conn){ conn. Открыть(); cmd. doStuff(); } продолжение. Open(); <- вы не пройдете дальше этой строки — она даже не будет компилироваться. Итак, опять же, хотя они похожи, они разные. Функционально то же самое — согласен, но это не тот вопрос, который я задаю. Я спрашиваю, почему это указывает на то, что я удаляю его дважды? по какой-то причине ссылка на мой тег не работает
4. Ах, я понимаю, значит, документация не совсем правильная. Инструмент анализа кода выдает вам ошибку по той же причине, по которой я пытался сказать вам, что они одинаковы — инструмент анализа кода считает, что они одинаковы, поэтому он думает, что если вы выполняете их оба, то вы удаляете дважды.
5. @alykins — Я не уверен, что документация точно неверна, но, возможно, вводит в заблуждение. Смотрите мои правки. Я бы рассматривал
Dispose()
иClose()
как одно и то же, использовалusing
блоки и не утруждал себя вызовомClose()
вручную в вашем использующем блоке;Dispose()
будет делать все, что нужно делать правильно. Интересный вопрос и находка, кстати.
Ответ №2:
Он информирует вас о том, что явное закрытие приводит к преждевременному расходованию ресурсов. Оператор using автоматически удалит его для вас.
Ответ №3:
Согласно MSDN:
Close и Dispose функционально эквивалентны.
Таким образом, вызов .Close()
удаляет объект. Кроме того, поскольку объект находится в using
блоке, компилятор также вызывает .Dispose()
. Требуется только одно из двух. (И в данном случае рекомендуется последнее.)
По сути, вам просто нужно удалить вызов, Close()
поскольку using
блок обработает это за вас, когда он избавится от объекта:
using (conn)
{
SqlCommand cmd = new SqlCommand("usp_myproc", conn);
cmd.CommandType = CommandType.StoredProcedure;
using(cmd)
{
cmd.Parameters.Add(new SqlParameter("@param", param));
conn.Open();
System.Data.SqlClient.SqlDataReader dr = cmd.ExecuteReader();
dt.Load(dr);
}
}
Комментарии:
1. Функционально не означает буквально, хотя — как в комментарии в другом ответе. Когда я закрываю его, он возвращается в пул соединений, затем я удаляю его — на самом деле я просто делаю больше работы для себя, но эта ошибка не должна появляться, поскольку она еще не удалена. Есть ли во мне смысл?
Ответ №4:
close();
и dispose();
по сути, делайте то же самое, вот почему вы получаете это предупреждение. Проверьте эту ссылку для получения дополнительной информации.