Зачем ловить и отбрасывать исключение в C #? - PullRequest
507 голосов
/ 19 мая 2009

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

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

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;
    }
}

Остальная часть статьи выглядит вменяемой и разумной (нубу), но этот try-catch-throw вызывает 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- соч.


EDIT:

Просто подведу итог всем, кто найдет эту ветку в будущем ...

НЕ

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

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

DO

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).


Ссылка:

Ответы [ 16 ]

394 голосов
/ 19 мая 2009

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

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

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

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}
108 голосов
/ 19 мая 2009

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

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

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

Либо делай,

try { ... }
catch { throw; }

OR

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
}
52 голосов
/ 19 мая 2009

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, если ErrorCode не был 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
}
13 голосов
/ 18 сентября 2009

Моя главная причина для того, чтобы иметь код как:

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

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

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

11 голосов
/ 19 мая 2009

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

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);
  }
}
10 голосов
/ 19 мая 2009

Разве это не эквивалентно вообще обрабатывать исключения?

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

8 голосов
/ 19 мая 2009

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

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

5 голосов
/ 14 октября 2013

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

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

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

3 голосов
/ 27 мая 2018

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

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

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

3 голосов
/ 09 августа 2016

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

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

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

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