Инструмент анализа кода

#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(); по сути, делайте то же самое, вот почему вы получаете это предупреждение. Проверьте эту ссылку для получения дополнительной информации.