C# с использованием ошибки перехвата оператора

StackOverflow https://stackoverflow.com/questions/248961

  •  05-07-2019
  •  | 
  •  

Вопрос

Я просто смотрю на оператор using. Я всегда знал, что он делает, но до сих пор не пробовал его использовать, я придумал следующий код:

 using (SqlCommand cmd = 
     new SqlCommand(reportDataSource, 
         new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)))
 {
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
     cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
     cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
     cmd.Connection.Open();

     DataSet dset = new DataSet();
     new SqlDataAdapter(cmd).Fill(dset);
     this.gridDataSource.DataSource = dset.Tables[0];
 }

Кажется, это работает, но есть ли в этом какой-то смысл, поскольку, насколько я могу судить, мне все равно нужно будет заключить это в блок try catch, чтобы поймать непредвиденные ошибки, например.SQL-сервер отключен.Я что-то пропустил?

Насколько я сейчас вижу, это просто мешает мне закрывать и удалять cmd, но строк кода будет больше, потому что try catch все еще нужен.

Это было полезно?

Решение

Этот код должен быть следующим, чтобы обеспечить своевременное закрытие соединения. Закрытие только команды не закрывает соединение:

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, con))
         {
             cmd.CommandType = CommandType.StoredProcedure;
             cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
             cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
             cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
             cmd.Connection.Open();

             DataSet dset = new DataSet();
             new SqlDataAdapter(cmd).Fill(dset);
             this.gridDataSource.DataSource = dset.Tables[0];
         }

Чтобы ответить на ваш вопрос, вы можете проделать то же самое в блоке finally, но это хорошо определяет код и гарантирует, что вы не забудете его очистить.

Другие советы

При выполнении операций ввода-вывода я пишу ожидаем исключение.

SqlConnection conn = null;
SqlCommand cmd = null;

try
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;

        conn.Open(); //opens connection

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch(Exception ex)
{
    Logger.Log(ex);
    throw;
}
finally
{
    if(conn != null)
        conn.Dispose();

        if(cmd != null)
        cmd.Dispose();
}

Редактировать . Чтобы быть в явном виде, я избегаю здесь блока using , потому что считаю важным входить в такие ситуации. Опыт научил меня, что вы никогда не знаете, какое странное исключение может появиться. Ведение журнала в этой ситуации может помочь вам обнаружить взаимоблокировку или определить, где изменение схемы влияет на немного использованную и мало проверенную часть вашей кодовой базы или на любое количество других проблем.

Правка 2: Можно утверждать, что блок использования может обернуть попытку / улов в этой ситуации, и это полностью допустимо и функционально эквивалентно. Это действительно сводится к предпочтениям. Вы хотите избежать лишних вложений за счет обработки вашего собственного захоронения? Или вы берете на себя дополнительное вложение, чтобы иметь автоматическое удаление. Я чувствую, что первое чище, поэтому я делаю это таким образом. Однако я не переписываю последнее, если найду его в кодовой базе, в которой я работаю.

Правка 3: Мне очень, очень хотелось бы, чтобы MS создала более явную версию использования (), которая сделала бы его более интуитивно понятным, что на самом деле происходит, и обеспечила большую гибкость в этом случае. Рассмотрим следующий воображаемый код:

SqlConnection conn = null;
SqlCommand cmd = null;

using(conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString),
          cmd = new SqlCommand(reportDataSource, conn)
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString);
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
        cmd.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch(Exception ex)
{
    Logger.Log(ex);
    throw;
}

Оператор using просто создает попытки try / finally с вызовами Dispose () в finally. Почему бы не дать разработчику унифицированный способ удаления и обработки исключений?

В этом случае может быть бесполезно использовать оператор using , если вы собираетесь try / catch / < код> наконец блокировать в любом случае. Как вы знаете, оператор using является синтаксическим сахаром для try / finally , который избавляется от объекта IDisposable . Если вы все равно захотите попробовать try / finally , вы, безусловно, можете выполнить Dispose самостоятельно.

Это в основном сводится к стилю: вашей команде может быть удобнее использовать с помощью операторов или с помощью , чтобы код выглядел чище.

Но, если шаблон в любом случае будет скрыт оператором using , продолжайте в том же духе и разберитесь сами, если вы этого хотите.

Если ваш код выглядит следующим образом:

using (SqlCommand cmd = new SqlCommand(...))
{
  try
  {
    /* call stored procedure */
  }
  catch (SqlException ex)
  {
    /* handles the exception. does not rethrow the exception */
  }
}

Тогда я бы реорганизовал его для использования try ... catch .. finally вместо этого.

SqlCommand cmd = new SqlCommand(...)
try
{
  /* call stored procedure */
}
catch (SqlException ex)
{
  /* handles the exception and does not ignore it */
}
finally
{
   if (cmd!=null) cmd.Dispose();
}

В этом сценарии я буду обрабатывать исключение, поэтому у меня нет выбора, кроме как добавить в этот try..catch, я мог бы также добавить предложение finally и сохранить себя на другом уровне вложенности. Обратите внимание, что я должен что-то делать в блоке catch, а не просто игнорировать исключение.

Более подробно о том, что сказал Крис Балланс, в разделе 15.13 спецификации C # (ECMA-334 версия 4) говорится, что «оператор использования» состоит из трех частей: получение, использование и утилизация. Использование ресурса неявно заключено в оператор try, который включает предложение finally. Это, наконец, пункт распоряжается ресурсом. Если нулевой ресурс получен, то вызов Dispose не выполняется и исключение не выдается. & Quot;

Описание близко к 2 страницам - стоит прочитать.

По моему опыту, SqlConnection / SqlCommand может генерировать ошибки так многими способами, что вам почти нужно обрабатывать исключения, которые выдают больше, чем ожидаемое поведение. Я не уверен, что хотел бы использовать здесь предложение using, так как я бы хотел сам обрабатывать случай нулевого ресурса.

использование не связано с отловом исключений. Речь идет о правильной утилизации ресурсов, которые находятся вне поля зрения сборщика мусора.

одна проблема с " использованием " в том, что он не обрабатывает исключения. если дизайнеры "используют" добавил бы "поймать" опционально с его синтаксисом, подобным приведенному ниже псевдокоду, это было бы гораздо полезнее:

using (...MyDisposableObj...)
{

   ... use MyDisposableObj ...

catch (exception)

   ... handle exception ...

}

it could even have an optional "finally" clause to cleanup anything other than the "MyDisposableObj" allocated at the beginning of the "using" statement... like:

using (...MyDisposableObj...)
{

   ... use MyDisposableObj ...
   ... open a file or db connection ...

catch (exception)

   ... handle exception ...

finally

   ... close the file or db connection ...

}

тем не менее не нужно будет писать код для удаления MyDisposableObj b / c, он будет обрабатываться используя ...

Как это сделать?

Да, вам все равно нужно ловить исключения. Преимущество блока using в том, что вы добавляете область действия в свой код. Вы говорите: «В этом блоке кода сделайте что-нибудь, а когда он дойдет до конца, закройте и утилизируйте ресурсы»

Это совсем не обязательно, но оно определяет ваши намерения для всех, кто использует ваш код, а также помогает не оставлять соединения и т. д. открытыми по ошибке.

Здесь есть много отличных ответов, но я не думаю, что это еще было сказано.

Не важно что...метод «Dispose» БУДЕТ вызван для объекта в блоке «using».Если вы вставите оператор возврата или выдадите ошибку, будет вызвана команда «Dispose».

Пример:

Я создал класс под названием «MyDisposable», он реализует IDisposable и просто выполняет Console.Write.Это всегда пишет в консоль даже во всех этих сценариях:

using (MyDisposable blah = new MyDisposable())
{
    int.Parse("!"); // <- calls "Dispose" after the error.

    return; // <-- calls Dispose before returning.
}

Оператор using фактически заменяется компилятором на блок try / finally, в котором параметр блока использования утилизируется, если он реализует интерфейс IDisposable. Помимо обеспечения правильной утилизации указанных объектов, когда они выпадают из области видимости, при использовании этой конструкции действительно не происходит захват ошибок.

Как упомянуто TheSoftwareJedi выше, вы должны убедиться, что объекты SqlConnection и SqlCommand утилизированы должным образом. Объединение обоих в один блок с использованием немного беспорядочно и может не сработать так, как вы думаете.

Также не забывайте использовать блок try / catch в качестве логики. Это кодовый запах, к которому мой нос испытывает особую неприязнь, и часто используется новичками или теми из нас, кто очень спешит, чтобы уложиться в срок.

К вашему сведению, в этом конкретном примере, поскольку вы используете соединение ADO.net и объект Command, имейте в виду, что оператор using просто выполняет Command.Dispose и Connection.Dispose (), которые фактически не закрывают соединение, но просто высвобождает его обратно в пул соединений ADO.net для повторного использования в следующем файле connection.open ... что хорошо, и абсолютно правильно, bc, если вы этого не сделаете, соединение останется неиспользуемым до тех пор, пока сборщик мусора не выпустит его обратно в пул, что может произойти только после многочисленных других запросов на подключение, которые в противном случае были бы вынуждены создавать новые подключения, даже если есть неиспользуемый, ожидающий сбора мусора.

Я бы принял решение о том, когда и когда не использовать оператор использования, в зависимости от ресурса, с которым я имею дело. В случае ограниченного ресурса, такого как соединение ODBC, я бы предпочел использовать T / C / F, чтобы я мог регистрировать значимые ошибки в момент их возникновения. Разрешение ошибкам драйвера базы данных возвращаться к клиенту и потенциально может быть потеряно при переносе исключений более высокого уровня.

T / C / F дает вам уверенность в том, что ресурс обрабатывается так, как вы хотите. Как уже упоминалось, оператор using не обеспечивает обработку исключений, он просто обеспечивает уничтожение ресурса. Обработка исключений - это недооцененная и недооцененная языковая структура, которая часто является различием между успехом и провалом решения.

Если вызывающая сторона вашей функции отвечает за работу с любыми исключениями, оператор using - это хороший способ обеспечить очистку ресурсов независимо от результата.

Он позволяет размещать код обработки исключений на границах слоев / сборок и помогает предотвратить перегруженность других функций.

Конечно, это действительно зависит от типов исключений, которые выдает ваш код. Иногда вы должны использовать try-catch-finally, а не оператор using. Моя привычка - всегда начинать с оператора using для IDisposables (или иметь классы, которые содержат IDisposables, также реализующие интерфейс) и добавлять try-catch-finally при необходимости.

Так что, в основном, "используя" точно такой же, как " Try / catch / finally " только гораздо более гибкий для обработки ошибок.

Незначительное исправление в примере: SqlDataAdapter также необходимо создать в выражении using :

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, con))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    con.Open();

    DataSet dset = new DataSet();
    using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
    {
        adapter.Fill(dset);
    }
    this.gridDataSource.DataSource = dset.Tables[0];
}

Во-первых, ваш пример кода должен быть следующим:

using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, conn))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    cmd.Connection.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}

При наличии кода в вашем вопросе исключение, создающее команду, приведет к тому, что только что созданное соединение не будет удалено. С учетом вышеизложенного, соединение правильно расположено.

Если вам нужно обработать исключения в конструкции соединения и команды (а также при их использовании), да, вам нужно обернуть все это в try / catch:

try
{
    using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
    using (SqlCommand cmd = new SqlCommand(reportDataSource, conn))
    {
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
        cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
        cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
        cmd.Connection.Open();

        DataSet dset = new DataSet();
        new SqlDataAdapter(cmd).Fill(dset);
        this.gridDataSource.DataSource = dset.Tables[0];
    }
}
catch (RelevantException ex)
{
    // ...handling...
}

Но вам не нужно заниматься очисткой conn или cmd ; это уже сделано для вас.

Сравните с тем же без , используя :

SqlConnection conn = null;
SqlCommand cmd = null;
try
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString);
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    cmd.Connection.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch (RelevantException ex)
{
    // ...handling...
}
finally
{
    if (cmd != null)
    {
        try
        {
            cmd.Dispose();
        }
        catch { }
        cmd = null;
    }
    if (conn != null)
    {
        try
        {
            conn.Dispose();
        }
        catch { }
        conn = null;
    }
}
// And note that `cmd` and `conn` are still in scope here, even though they're useless

Я знаю, что лучше написать. : -)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top