Looking for c# Keywords? Try Ask4Keywords

C# Language Исключительные фильтры


пример

Фильтры исключений дают разработчикам возможность добавить условие (в виде boolean выражения) в блок catch , позволяя catch выполнять, только если условие оценивается как true .

Фильтры исключений позволяют распространять информацию об отладке в исходном исключении, где при использовании оператора if внутри блока catch и повторного выброса исключения останавливает распространение отладочной информации в исходном исключении. При использовании фильтров исключений исключение продолжает распространяться вверх в стеке вызовов, если условие не выполняется. В результате фильтры исключений значительно облегчают процесс отладки. Вместо того, чтобы останавливаться на инструкции throw , отладчик остановится на выражении, исключающем исключение, с сохранением текущего состояния и всех локальных переменных. Аналогичным образом затрагиваются аварийные свалки.

Фильтры исключений поддерживались CLR с самого начала, и они были доступны из VB.NET и F # в течение более десяти лет, подвергая часть модели обработки исключений CLR. Только после выпуска C # 6.0 функциональность также была доступна для разработчиков C #.


Использование фильтров исключений

Фильтры исключений используются путем добавления предложения when в выражение catch . Можно использовать любое выражение, возвращающее bool в предложении when (кроме ожидания ). Объявленная переменная exception ex доступна из предложения when :

var SqlErrorToIgnore = 123;
try
{
    DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
    throw new Exception("An error occurred accessing the database", ex);
}

Множество блоков catch when предложения могут быть объединены. Первое, when предложение, возвращающее true приведет к тому, что исключение будет обнаружено. Его блок catch будет введен, в то время как другие предложения catch будут проигнорированы (их, when предложения не будут оцениваться). Например:

try
{ ... }
catch (Exception ex) when (someCondition) //If someCondition evaluates to true,
                                          //the rest of the catches are ignored.
{ ... }
catch (NotImplementedException ex) when (someMethod()) //someMethod() will only run if
                                                       //someCondition evaluates to false
{ ... }
catch(Exception ex) // If both when clauses evaluate to false
{ ... }

Опасно, когда статья

предосторожность

Может быть опасно использовать фильтры исключений: когда Exception выбрано из предложения when , Exception из предложения when игнорируется и считается false . Такой подход позволяет разработчикам писать, when клаузулы, не заботясь о недопустимых случаях.

Следующий пример иллюстрирует такой сценарий:

public static void Main()
{
    int a = 7;
    int b = 0;
    try
    {
        DoSomethingThatMightFail();
    }
    catch (Exception ex) when (a / b == 0)
    {
        // This block is never reached because a / b throws an ignored
        // DivideByZeroException which is treated as false.
    }
    catch (Exception ex)
    {
        // This block is reached since the DivideByZeroException in the 
        // previous when clause is ignored.
    }
}

public static void DoSomethingThatMightFail()
{
    // This will always throw an ArgumentNullException.
    Type.GetType(null);
}

Посмотреть демо

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

1. int a = 0, b = 0;
2. try {
3.     int c = a / b;
4. }
5. catch (DivideByZeroException) {
6.     throw;
7. }

Номер строки исключений сообщается как 6, потому что ошибка была поймана и повторно выбрана с помощью оператора throw в строке 6.

То же самое происходит с фильтрами исключений:

1. int a = 0, b = 0;
2. try {
3.     int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6.     throw;
7. }

В этом примере a равно 0, тогда предложение catch игнорируется, но 3 указывается как номер строки. Это происходит потому, что они не разматывают стек . Более конкретно, исключение не попадает в строку 5, потому a на самом деле оно равно 0 и, следовательно, нет возможности исключить исключение в строке 6, потому что строка 6 не выполняется.


Регистрация в качестве побочного эффекта

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

Имейте в виду, что, хотя это, кажется, удобный способ ведения журнала, это может быть рискованным, особенно если используются сторонние протоколирующие сборки. Они могут генерировать исключения при входе в не очевидные ситуации, которые могут быть легко обнаружены (см. Рискованное, when(...) выше).

try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred"))
{
    // This catch block will never be reached
}

// ...

static bool Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
    return false;
}

Посмотреть демо

Общий подход в предыдущих версиях C # заключался в регистрации и повторном выбросе исключения.

6,0
try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex)
{
     Log(ex, "An error occurred");
     throw;
}

// ...

static void Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
}

Посмотреть демо


finally блок

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

Пример: finally блок

private static bool Flag = false;

static void Main(string[] args)
{
    Console.WriteLine("Start");
    try
    {
        SomeOperation();
    }
    catch (Exception) when (EvaluatesTo())
    {
        Console.WriteLine("Catch");
    }
    finally
    {
        Console.WriteLine("Outer Finally");
    }
}

private static bool EvaluatesTo()
{
    Console.WriteLine($"EvaluatesTo: {Flag}");
    return true;
}

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

Производительность:

Начните
EvaluatesTo: True
Внутренний Наконец
Ловить
Внешнее окончание

Посмотреть демо

В приведенном выше примере, если метод SomeOperation не желает «утечки», глобальное состояние изменяется на предложения вызывающего, when оно должно быть, оно также должно содержать блок catch для изменения состояния. Например:

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    catch
    {
       Flag = false;
       throw;
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

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