Какие преимущества дает новая функция "Фильтр исключений"?
В 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 - это небольшие улучшения и синтаксический сахар, и хотя фильтры исключений не очень большая функция, они предоставляют функциональность, которая была невозможна раньше.
Другое улучшение исключений в C# 6.0 - поддержка фильтров исключений - приводит язык в соответствие с другими языками.NET, а именно Visual Basic .NET и F#
Фильтры исключений предпочтительнее, чем перехват и перебрасывание, потому что они оставляют стек без повреждений. Если позднее исключение вызывает сброс стека, вы можете увидеть, откуда он изначально пришел, а не только в последнем месте, где он был переброшен.
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
}