Зачем ловить и перестраивать исключение в C#?



Я смотрю на статьи C# - Объект Передачи Данных на сериализуемые объекты переноса данных.



статья включает в себя этот кусок кода:



public static string SerializeDTO(DTO dto) {
try {
XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
StringWriter sWriter = new StringWriter();
xmlSer.Serialize(sWriter, dto);
return sWriter.ToString();
}
catch(Exception ex) {
throw ex;
}
}


остальная часть статьи выглядит разумной и разумной (для noob), но эта попытка поймать бросок бросает исключение WtfException... разве это не равносильно тому, чтобы вообще не обрабатывать исключения?



Ergo:



public static string SerializeDTO(DTO dto) {
XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
StringWriter sWriter = new StringWriter();
xmlSer.Serialize(sWriter, dto);
return sWriter.ToString();
}


или я упускаю что-то фундаментальное о обработка ошибок в C#? Это почти то же самое, что Java (минус проверенные исключения), не так ли? ... То есть они оба доработали C++.



вопрос переполнения стека разница между повторным броском параметра-менее поймать и ничего не делать? кажется, поддерживает мое утверждение, что try-catch-throw-это no-op.





EDIT:



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



DO Не



try {
// Do stuff that might throw an exception
}
catch (Exception e) {
throw e; // This destroys the strack trace information!
}


информация трассировки стека может иметь решающее значение для определения основной причины проблемы!



сделать



try {
// Do stuff that might throw an exception
}
catch (SqlException e) {
// Log it
if (e.ErrorCode != NO_ROW_ERROR) { // filter out NoDataFound.
// Do special cleanup, like maybe closing the "dirty" database connection.
throw; // This preserves the stack trace
}
}
catch (IOException e) {
// Log it
throw;
}
catch (Exception e) {
// Log it
throw new DAOException("Excrement occurred", e); // wrapped & chained exceptions (just like java).
}
finally {
// Normal clean goes here (like closing open files).
}


поймать более конкретные исключения перед менее конкретными (так же, как Java).





ссылки:



773   16  

16 ответов:

во-первых; то, как код в статье делает это зло. throw ex сбросит стек вызовов в исключении до точки, где находится этот оператор throw; потеря информации о том, где фактически было создано исключение.

во-вторых, если вы просто поймаете и снова бросите так, я не вижу никакой добавленной стоимости, приведенный выше пример кода будет так же хорош (или, учитывая throw ex бит, еще лучше) без try-catch.

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

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}

не делай этого,

try 
{
...
}
catch(Exception ex)
{
   throw ex;
}

вы потеряете информацию трассировки стека...

либо делать,

try { ... }
catch { throw; }

или

try { ... }
catch (Exception ex)
{
    throw new Exception("My Custom Error Message", ex);
}

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

try
{
   ...
}
catch(SQLException sex)
{
   //Do Custom Logging 
   //Don't throw exception - swallow it here
}
catch(OtherException oex)
{
   //Do something else
   throw new WrappedException("Other Exception occured");
}
catch
{
   System.Diagnostics.Debug.WriteLine("Eeep! an error, not to worry, will be handled higher up the call stack");
   throw; //Chuck everything else back up the stack
}

C# (до C# 6) не поддерживает CIL "отфильтрованные исключения", что делает VB, поэтому в C# 1-5 одна из причин повторного запуска исключения заключается в том, что у вас недостаточно информации во время catch (), чтобы определить, хотите ли вы на самом деле поймать исключение.

например, в VB вы можете сделать

Try
 ..
Catch Ex As MyException When Ex.ErrorCode = 123
 .. 
End Try

...который не будет обрабатывать MyExceptions с различными значениями ErrorCode. В C# до v6 вам нужно будет поймать и повторно бросить MyException, если Код ошибки не был 123:

try 
{
   ...
}
catch(MyException ex)
{
    if (ex.ErrorCode != 123) throw;
    ...
}

начиная с C# 6.0 вы можете фильтровать так же, как с VB:

try 
{
  // Do stuff
} 
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
  // Handle, other exceptions will be left alone and bubble up
}

моя основная причина наличия кода, как:

try
{
    //Some code
}
catch (Exception e)
{
    throw;
}

Так что я могу иметь точку останова в catch, которая имеет экземпляр объекта исключения. Я делаю это много во время разработки/отладки. Конечно, компилятор дает мне предупреждение обо всех неиспользуемых e, и в идеале они должны быть удалены до сборки выпуска.

они хороши во время отладки, хотя.

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

public static string SerializeDTO(DTO dto) {
  try {
      XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
      StringWriter sWriter = new StringWriter();
      xmlSer.Serialize(sWriter, dto);
      return sWriter.ToString();
  }
  catch(Exception ex) {
    string message = 
      String.Format("Something went wrong serializing DTO {0}", DTO);
    throw new MyLibraryException(message, ex);
  }
}

разве это точно не эквивалентно не обработка исключений вообще?

не совсем, это не то же самое. Он сбрасывает stacktrace исключения. Хотя я согласен, что это, вероятно, ошибка, и, следовательно, пример плохого кода.

вы не хотите бросать ex - так как это потеряет стек вызовов. Смотрите Обработка Исключений (MSDN).

и да, попробовать...catch не делает ничего полезного (кроме потери стека вызовов - так что это на самом деле хуже - если по какой-то причине вы не хотите раскрывать эту информацию).

точка, о которой люди не упоминали, заключается в том, что, хотя языки .NET на самом деле не делают надлежащего различия, вопрос о том, следует ли принять меры когда происходит исключение, и будет ли он разрешить это, на самом деле разные вопросы. Есть много случаев, когда нужно принять меры, основанные на исключениях, которые у вас нет надежды разрешить, и есть некоторые случаи, когда все, что необходимо для "разрешения" исключения, - это размотать стек до определенного момента-никаких дальнейших действий не требуется.

потому что здравый смысл подсказывал, что надо только "поймать" все можно "уладить", много кода, который должен принять меры при возникновении исключений нет. Например, много код будет пытаться получить блокировку, поставить охраняемого объекта "временно" в состояние, которое нарушает его инвариантов, то положи предмет в законное состояние, и затем отпустите замок, прежде чем кто-либо другой не может видеть объект. Если исключение происходит во время объект находится в опасно-недопустимом состоянии, обычной практикой является освобождение блокировки с объектом, все еще находящимся в этом состоянии. Гораздо лучше было бы иметь исключение, которое происходит, когда объект находится в" опасном " состоянии, явно аннулирует блокировку, поэтому любая будущая попытка получить его немедленно потерпит неудачу. Последовательное использование такого шаблона значительно улучшило бы безопасность так называемой обработки исключений" покемонов", которая IMHO получает плохую репутацию в первую очередь из-за кода, который позволяет исключениям просачиваться без принятия соответствующих мер в первую очередь.

в большинстве языков .NET единственным способом выполнения кода на основе исключения является catch Он (даже если он знает, что не собирается разрешать исключение), выполните действие, о котором идет речь, а затем повторноthrow). Другой возможный подход, если код не заботится о том, что исключение является использование ok флаг с try/finally заблокировать; выберите ok флаг false перед блоком, и чтобы true перед выходом блока, и перед любым return это внутри блока. Затем, внутри finally предположим, что если ok Не задано, должно быть, произошло исключение. Такой подход семантически лучше, чем catch/throw, но уродлив и менее ремонтопригоден, чем это должно быть.

одна из возможных причин catch-throw заключается в отключении любых фильтров исключений глубже стека от фильтрации вниз ( случайная старая ссылка). Но, конечно, если бы это было намерение, там был бы комментарий, говорящий об этом.

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

скажете вы Catch io.FileNotFoundExeption ex а затем использовать альтернативный путь к файлу или какой-то такой, но все равно бросить ошибку.

делаем Throw вместо Throw Ex позволяет сохранить полную трассировку стека. Throw ex перезапускает трассировку стека из оператора throw (я надеюсь, что это имеет смысл).

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

примером этого является метод, в котором вы устанавливаете курсор (например, на курсор ожидания), метод имеет несколько точек выхода (например, если () return;), и вы хотите, чтобы курсор был сброшен в конце метода.

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

try
{
    Cursor.Current = Cursors.WaitCursor;
    // Test something
    if (testResult) return;
    // Do something else
}
catch
{
    throw;
}
finally
{
     Cursor.Current = Cursors.Default;
}

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

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

Я думаю, что это просто используется, чтобы брошенные исключения были чище и не входили в "корни" библиотеки.

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

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

для повторной итерации, хотя нет смысла ловить исключение в Примере, который вы опубликовали. Не делай этого так!

Извините, но многие примеры, как "улучшенный дизайн" по-прежнему пахнет ужасно или может быть чрезвычайно вводящим в заблуждение. Пытаться { } поймать { log; throw } просто совершенно бессмысленно. Регистрация исключений должна выполняться в центральном месте внутри приложения. исключения пузырятся в stacktrace в любом случае, почему бы не зарегистрировать их где-нибудь и близко к границам системы?

внимание следует использовать, когда вы сериализуете свой контекст (т. е. DTO в одном данном примере) только в сообщение журнала. Оно может легко содержать конфиденциальную информацию можно не хотеть, чтобы добраться до рук всех людей, которые могут получить доступ к файлам журнала. И если вы не добавляете новую информацию к исключению, я действительно не вижу смысла в обертывании исключений. Старая добрая Java имеет некоторый смысл для этого, она требует, чтобы вызывающий знал, какие исключения следует ожидать, затем вызывая код. Поскольку у вас нет этого в .NET, обертывание не приносит никакой пользы, по крайней мере, в 80% случаев, которые я видел.

в дополнение к тому, что сказали другие, см. мой ответ: к связанному вопросу, который показывает, что перехват и переосмысление не являются no-op (это в VB, но некоторые из кода могут быть вызваны C# из VB).

большинство ответов говорят о сценарии catch-log-rethrow.

вместо того, чтобы писать его в своем коде, рассмотрите возможность использования AOP, в частности Postsharp.Диагностический.Инструментарий С OnExceptionOptions IncludeParameterValue и IncludeThisArgument

Comments

    Ничего не найдено.