Перехват исключений с помощью "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" обработку исключений быстрее, потому что обработчик пропускается как таковой, и размотка стека может произойти намного раньше, чем при обработке конкретных случаев использования в обработчике? Существуют ли какие-либо конкретные случаи использования, которые лучше подходят для этой функции, которые люди могут затем принять в качестве хорошая практика?

607   3  

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

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