Какие преимущества дает новая функция "Фильтр исключений"?

В C# 6 появилась новая функция под названием "фильтрация исключений"

Синтаксис такой:

catch (Win32Exception exception) when (exception.NativeErrorCode == 0x00042)  
{  
    //Do something here 
}  

Я не мог не задаться вопросом, какая польза от этого по сравнению с нынешним подходом:

catch (Win32Exception exception)   
{
     if (exception.NativeErrorCode == 0x00042)
     {
          //Do something here 
     }
}

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

5 ответов

Решение

Функция фильтров исключений в C# 6.0 предоставляет различные преимущества. Вот объяснение некоторых (упорядочено по моей предполагаемой важности)

  • Функция Parity - Фильтры исключений уже были реализованы на уровне IL и других языках.Net (VB.Net & F#)[1] и как часть создания нового компилятора для C# и VB.Net (проект "Roslyn") многих функций. существующие на одном языке и отсутствующие на другом были реализованы[2].

  • Crash Dumps - Фильтры исключений не изменяют стек. это означает, что если он будет сброшен (в аварийном дампе), вы сможете узнать, где было изначально сгенерировано исключение, а не только где оно было переброшено (что не имеет отношения к реальной проблеме)[3]

  • Отладка - когда исключение входит в catch блокировать, перебрасывать используя throw; и не обрабатывается где-либо еще в стеке (и настройки исключения устанавливаются как разрывы, когда исключение не обрабатывается пользователем), отладчик будет прерываться на throw; вместо того, где изначально генерируется исключение (т.е. в приведенном ниже примере оно будет разбито на throw; и не throw new FileNotFoundException();)

  • множественный catch Блоки - без фильтров исключений вы должны перехватить исключение, проверить условие и, если оно не выполнено throw; исключение. Переброшенное исключение не учитывает других catch блоки, даже если исключение удовлетворяет всем условиям, что в конечном итоге приводит к одному большому catch блок

    try
    {
        throw new FileNotFoundException();
    }
    catch (FileNotFoundException e)
    {
        if (!e.FileName.Contains("Hamster"))
        {
            throw;
        }
        // Handle
    }
    catch (Exception e)
    {
        // Unreachable code
    }
    
  • Читаемость - хотя вы могли бы использовать "поймать все" catch блок со многими условиями и throw; когда они не встречаются (хотя и претерпевают изменения в стеке), гораздо яснее иметь отдельные и отличные друг от друга catch блоки, где каждый обрабатывает определенную проблему соответствующим образом:

    try
    {
    }
    catch (Win32Exception exception) when (exception.NativeErrorCode == 0x00042)  
    { 
    }  
    catch (Win32Exception exception) when (exception.NativeErrorCode == 0x00011)  
    {  
    }
    catch (IOException)
    {  
    }
    
  • "Abuse" - вы можете использовать фильтры исключений как способ проверки исключения без его обработки. Хотя это не главное преимущество, это хороший побочный эффект. Вы можете иметь метод регистрации с ложным возвратом и пустой catch блок:

    private static bool LogException(Exception exception)
    {
        Console.WriteLine(exception.Message);
        return false;
    }
    
    try
    {
    }
    catch (ArgumentException exception) when (LogException(exception))  
    {
        // Unreachable code.
    }
    

В заключение, большинство функций C# 6.0 - это небольшие улучшения и синтаксический сахар, и хотя фильтры исключений не очень большая функция, они предоставляют функциональность, которая была невозможна раньше.


  1. AC# 6.0 Language Preview

    Другое улучшение исключений в C# 6.0 - поддержка фильтров исключений - приводит язык в соответствие с другими языками.NET, а именно Visual Basic .NET и F#

  2. Языковые особенности в C# 6 и VB 14

  3. Новые функции в C# 6

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

Eren Ersönmez уже упомянул самое важное преимущество.

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

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

Так что если у вас есть это:

try
{
    DoSomethingThatThrows();
}
catch (Win32Exception exception)   
{
     if (exception.NativeErrorCode == 0x00042)
     {
          //Do something here 
     }
     else
     {
         throw;
     }
}

отладчик остановится throw когда NativeErrorCode не 0x42.

Но если у вас есть это:

try
{
    DoSomethingThatThrows();
}
catch (Win32Exception exception) if (exception.NativeErrorCode == 0x00042)
{         
     //Do something here 
}

отладчик остановится в DoSomethingThatThrows (или в вызываемом им методе), позволяя вам легче увидеть, что вызвало ошибку (опять же, когда NativeErrorCode не 0x42)

Из официальных описаний функций C# (ссылка на скачивание PDF, как на VS2015 Preview):

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

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

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

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

Реальная выгода ИМО:

try
{
}
catch (SomeException ex) if (ex.Something)
{
}
catch (Exception ex)
{
  // SomeException with !ex.Something is caught here
  // not the same as calling `throw` for !ex.Something 
  // without filters in previous handler
  // less code duplication
}
Другие вопросы по тегам