Перехват исключений с помощью "catch, when"
я наткнулся на эту новую функцию в C#, которая позволяет обработчику catch выполняться при выполнении определенного условия.
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;
}
делает ли использование функции "catch, when" обработку исключений быстрее, потому что обработчик пропускается как таковой, и размотка стека может произойти намного раньше, чем при обработке конкретных случаев использования в обработчике? Существуют ли какие-либо конкретные случаи использования, которые лучше подходят для этой функции, которые люди могут затем принять в качестве хорошая практика?
3 ответов:
поймать блоки уже позволяют фильтровать на тип исключения:
catch (SomeSpecificExceptionType e) {...}The
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; } }который, возможно, менее элегантный и слегка ломает трассировку стека.
кроме того, вы можете упомянуть то же самое тип исключения дважды в одном и том же try-catch-block:
try { SomeLegacyComOperation(); } catch (COMException e) when (e.ErrorCode == 0x1234) { ... } catch (COMException e) when (e.ErrorCode == 0x5678) { ... }что было бы невозможно без
whenсостояние.
от Roslyn это 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с рамой: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')эта информация может быть очень полезна при отладке аварийных дампов.
при возникновении исключения, первый проход обработки исключений определяет, где исключение будет поймано до разматывание стека; если/когда определено местоположение "catch", выполняются все блоки "finally" (обратите внимание, что если исключение экранирует блок "finally", обработка более раннего исключения может быть отменена). Как только это произойдет, код возобновит выполнение в "catch".
если есть точка останова в функции, которая оценивается как часть "когда", эта точка останова приостановит выполнение до того, как произойдет размотка стека; напротив, точка останова в "catch" будет только приостанавливать выполнение после всех
finallyобработчики выполнения.наконец, если строки 23 и 27 из
fooвызовbar, и вызов на линии 23 вызывает исключение, которое поймано вfooи перестроен в строке 57, то трассировка стека будет предполагать, что исключение произошло при вызовеbarиз строки 57 [расположение rethrow], уничтожение любой информации о том, произошло ли исключение в вызове line-23 или line-27. Используяwhenчтобы избежать ловли исключение в первую очередь избегает такого нарушения.кстати, полезный шаблон, который раздражающе неудобен как в C#, так и VB.NET это использовать вызов функции в пределах
whenпредложение для установки переменной, которая может быть использована в пределахfinallyпредложение, чтобы определить, является ли функция завершена нормально, для обработки случаев, когда функция не имеет никакой надежды "разрешение" любого исключения, которое происходит, но тем не менее должно принимать меры на его основе. Например, если исключение создается внутри метода фабрики, который должен возвращать объект, инкапсулирующий ресурсы, все полученные ресурсы должны быть освобождены, но базовое исключение должно просачиваться до вызывающего объекта. Самый чистый способ справиться с этим семантически (но не синтаксически) - этоfinallyблок проверьте, произошло ли исключение и, если да, отпустите все ресурсы, полученные от имени объекта, который больше не будет возвращен. Поскольку код очистки не имеет никакой надежды на разрешение любого условия, вызвавшего исключение, он действительно не долженcatchэто, но просто нужно знать, что произошло. Вызов функции типа:bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second) { first = second; return false; }внутри
whenпредложение позволит заводской функции знать что-то случилось.
Comments