2016-07-21 6 views
69

Я столкнулся с этой новой функцией в C#, которая позволяет обработчику уловов выполнить, когда выполняется конкретное условие.Ловля исключений с «catch, when»

int i = 0; 
try 
{ 
    throw new ArgumentNullException(nameof(i)); 
} 
catch (ArgumentNullException e) 
when (i == 1) 
{ 
    Console.WriteLine("Caught Argument Null Exception"); 
} 

Я пытаюсь понять, когда это может когда-либо быть полезным.

Один сценарий может быть что-то вроде этого:

try 
{ 
    DatabaseUpdate() 
} 
catch (SQLException e) 
when (driver == "MySQL") 
{ 
    //MySQL specific error handling and wrapping up the exception 
} 
catch (SQLException e) 
when (driver == "Oracle") 
{ 
    //Oracle specific error handling and wrapping up of exception 
} 
.. 

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

Другой сценарий, который я могу думать что-то вроде:

try 
{ 
    SomeOperation(); 
} 
catch(SomeException e) 
when (Condition == true) 
{ 
    //some specific error handling that this layer can handle 
} 
catch (Exception e) //catchall 
{ 
    throw; 
} 

Опять же, это то, что я могу сделать, как:

try 
{ 
    SomeOperation(); 
} 
catch(SomeException e) 
{ 
    if (condition == true) 
    { 
     //some specific error handling that this layer can handle 
    } 
    else 
     throw; 
} 

ли с помощью «поймать, когда» особенность делает исключение быстрее обрабатывается, потому что обработчик пропущен как таковой, и раскручивание стека может произойти намного раньше, чем по сравнению с обработкой конкретных случаев использования в обработчике? Существуют ли какие-либо конкретные варианты использования, которые лучше подходят для этой функции, которую люди могут принять в качестве хорошей практики?

+5

Это полезно, если 'when' должен получить доступ к самому исключению –

+1

Но это то, что мы можем сделать и в самом блоке обработчика, а также правильно. Есть ли какие-либо преимущества, кроме «немного более организованного кода»? – Madhusudhan

+3

Но тогда вы уже обработали исключение, которое вы не хотите. Что делать, если вы хотите поймать его где-нибудь еще в этом 'try..catch ... catch..catch..finally'? –

ответ

70

Поймать блоки уже позволяют фильтровать на типа из исключения:

catch (SomeSpecificExceptionType e) {...} 

Предложение when позволяет расширить этот фильтр для общих выражений.

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


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

Вот случай, который я на самом деле используется (в VB, который уже имеет эту функцию в течение некоторого времени):

try 
{ 
    SomeLegacyComOperation(); 
} 
catch (COMException e) when (e.ErrorCode == 0x1234) 
{ 
    // Handle the *specific* error I was expecting. 
} 

То же самое для SqlException, который также имеет свойство ErrorCode. Альтернативой было бы что-то подобное:

try 
{ 
    SomeLegacyComOperation(); 
} 
catch (COMException e) 
{ 
    if (e.ErrorCode == 0x1234) 
    { 
     // Handle error 
    } 
    else 
    { 
     throw; 
    } 
} 

, который, возможно, менее элегантно и slightly breaks the stack trace.

Кроме того, можно отметить и тот же тип исключения дважды в одной и той же попробуй поймать-блока:

try 
{ 
    SomeLegacyComOperation(); 
} 
catch (COMException e) when (e.ErrorCode == 0x1234) 
{ 
    ... 
} 
catch (COMException e) when (e.ErrorCode == 0x5678) 
{ 
    ... 
} 

, которые не были бы возможны без условия when.

+2

Второй подход также не позволяет поймать его в другом 'catch', не так ли? –

+0

@TimSchmelter. Правда. Вам придется обрабатывать * all * COMExceptions в том же блоке. – Heinzi

+0

Хотя 'when' позволяет обрабатывать один и тот же тип исключения несколько раз. Вы должны упомянуть об этом, так как это ключевое различие. Без 'when' вы получите ошибку компилятора. –

31

От wiki (курсив мой) Рослин в:

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

Это также распространенная и принятая форма «злоупотребления» для использования исключений фильтры для побочных эффектов; например Ведение журнала. Они могут проверить исключение «пролетел» без перехвата его курса. В тех случаях, фильтра часто будет вызов к ложной возвращающему вспомогательной функции, которая выполняет побочные эффекты:

private static bool Log(Exception e) { /* log it */ ; return false; } 

… try { … } catch (Exception e) when (Log(e)) { } 

Первый пункт стоит продемонстрировать.

static class Program 
{ 
    static void Main(string[] args) 
    { 
     A(1); 
    } 

    private static void A(int i) 
    { 
     try 
     { 
      B(i + 1); 
     } 
     catch (Exception ex) 
     { 
      if (ex.Message != "!") 
       Console.WriteLine(ex); 
      else throw; 
     } 
    } 

    private static void B(int i) 
    { 
     throw new Exception("!"); 
    } 
} 

Если мы запустим это в WinDbg, пока исключение не попал, и распечатать стек с помощью !clrstack -i -a мы увидим только кадр A:

003eef10 00a7050d [DEFAULT] Void App.Program.A(I4) 

PARAMETERS: 
    + int i = 1 

LOCALS: 
    + System.Exception ex @ 0x23e3178 
    + (Error 0x80004005 retrieving local variable 'local_1') 

Однако, если мы изменим программу использовать when:

catch (Exception ex) when (ex.Message != "!") 
{ 
    Console.WriteLine(ex); 
} 

Мы видим стек также содержит рамку B «S:

001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4) 

PARAMETERS: 
    + int i = 2 

LOCALS: (none) 

001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4) 

PARAMETERS: 
    + int i = 1 

LOCALS: 
    + System.Exception ex @ 0x2213178 
    + (Error 0x80004005 retrieving local variable 'local_1') 

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

+6

Это меня удивляет. Не будет 'throw;' (в отличие от 'throw ex;') также оставить невредимым стек? +1 для побочных эффектов вещь. Я не уверен, что я одобряю это, но хорошо знать об этой технике. – Heinzi

+0

Да. бросить; сохраняет исходную информацию о том, где была запущена процедура. Это кажется неправильным. – Madhusudhan

+9

Это не так - это не относится к стеку * trace * - это относится к самому стеку. Если вы посмотрите на стек в отладчике (WinDbg), и даже если вы использовали 'throw;', стек распадается и вы потеряете значения параметров. –

4

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

Если в функции, которая оценивается как часть «когда», есть точка останова, эта точка останова приостанавливает выполнение до того, как произойдет сброс стека; напротив, точка останова при «улове» будет приостановлена ​​только после завершения всех обработчиков finally.

Наконец, если строки 23 и 27 foo вызова bar и вызова на линии 23 генерирует исключение, которое перехватывается в пределах foo вызваны повторно на линии 57, а затем трассировки стека будет предположить, что произошло исключение при вызове bar из line 57 [location of rethrow], уничтожая любую информацию о том, произошло ли исключение в вызове line-23 или line-27. Использование when во избежание исключения исключения в первую очередь позволяет избежать такого нарушения.

BTW, полезный образец, который раздражает неудобно как на C#, так и на VB.NET - использовать вызов функции в предложении when, чтобы установить переменную, которая может использоваться в предложении finally, чтобы определить, нормально ли выполнялась функция, для обработки случаев, когда функция не надеется «разрешить» любое исключение, которое возникает, но должно тем не менее, принимают меры на его основе. Например, если исключение выбрано в заводском методе, который должен возвращать объект, который инкапсулирует ресурсы, любые ресурсы, которые были получены, должны быть освобождены, но основное исключение должно просачиваться до вызывающего. Самый чистый способ обработки семантически (хотя и не синтаксически) состоит в том, чтобы блок finally проверял, произошло ли исключение, и если да, освободите все ресурсы, полученные от имени объекта, который больше не будет возвращен. Поскольку код очистки не имеет надежды на разрешение любого условия, вызвавшего исключение, он действительно не должен catch, но просто нужно знать, что произошло. Вызов функции, как:

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second) 
{ 
    first = second; 
    return false; 
} 

в пункте when позволит для функции завода знать что что-то произошло.

Смежные вопросы