Поймать несколько исключений одновременно? - PullRequest
1901 голосов
/ 26 сентября 2008

Не рекомендуется просто ловить System.Exception. Вместо этого должны быть обнаружены только «известные» исключения.

Теперь это иногда приводит к ненужному повторяющемуся коду, например:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Интересно: есть ли способ перехватить оба исключения и вызвать WebId = Guid.Empty только один раз?

Данный пример довольно прост, так как это всего лишь GUID. Но представьте себе код, в котором вы несколько раз модифицируете объект, и если одна из манипуляций не удалась ожидаемым образом, вы хотите «сбросить» object. Однако, если есть непредвиденное исключение, я все еще хочу бросить это выше.

Ответы [ 28 ]

1943 голосов
/ 26 сентября 2008

Поймай System.Exception и включи типы

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}
465 голосов
/ 12 октября 2013

РЕДАКТИРОВАТЬ: Я согласен с другими, которые говорят, что, начиная с C # 6.0, фильтры исключений теперь являются идеальным способом: catch (Exception ex) when (ex is ... || ex is ... )

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

catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

ОРИГИНАЛ:

Я знаю, что немного опоздал на вечеринку здесь, но святой дым ...

Если перейти прямо к погоне, этот вид дублирует более ранний ответ, но если вы действительно хотите выполнить общее действие для нескольких типов исключений и сохранить все в чистоте и порядке в рамках одного метода, почему бы не просто использовать лямбда / закрытие / встроенную функцию, чтобы сделать что-то вроде следующего? Я имею в виду, что вполне вероятно, что в итоге вы поймете, что просто хотите сделать это закрытие отдельным методом, который вы можете использовать повсюду. Но тогда это будет очень легко сделать без реального структурного изменения остальной части кода. Правильно?

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

Я не могу не задаться вопросом ( предупреждение: впереди немножко иронии / сарказма), почему, черт возьми, приложить все усилия, чтобы просто заменить следующее:

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

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

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

Потому что он, безусловно, не будет автоматически более читабельным.

Конечно, я оставил три идентичных экземпляра /* write to a log, whatever... */ return; из первого примера.

Но это моя точка зрения. Вы все слышали о функциях / методах, верно? Шутки в сторону. Напишите общую функцию ErrorHandler и, как ее, вызывайте из каждого блока catch.

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

Этап обслуживания для тех, кто может быть относительно новичком в программировании, будет составлять 98,7% или более от общего срока службы вашего проекта, и бедняга, выполняющий обслуживание, почти наверняка будет кем-то другим, кроме вас. , И есть очень хороший шанс, что они будут тратить 50% своего времени на работу, ругаясь на ваше имя.

И, конечно, FxCop лает на вас, и поэтому вам нужно также добавить атрибут к вашему коду, который имеет точный zip-код для работающей программы и предназначен только для попросите FxCop игнорировать проблему, которая в 99,9% случаев является абсолютно правильной при маркировке. И, извините, я могу ошибаться, но разве этот атрибут «игнорировать» не скомпилирован в ваше приложение?

Поможет ли поместить весь тест if в одну строку, чтобы сделать его более читабельным? Я так не думаю. Я имею в виду, у меня когда-то был другой программист, яростно споривший однажды, что размещение большего количества кода в одной строке заставит его «работать быстрее». Но, конечно, он был совершенно безумным. Попытка объяснить ему (с открытым лицом - что было непросто), как интерпретатор или компилятор разбил бы эту длинную строку на дискретные операторы по одной инструкции на строку - по существу, идентичные результату, если бы он пошел вперед и просто сделал код читабельным вместо того, чтобы пытаться перехитрить компилятор - не оказал на него никакого влияния. Но я отвлекся.

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

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

Просто говорю ...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}
303 голосов
/ 04 апреля 2014

Как уже отмечали другие, вы можете иметь оператор if внутри блока catch, чтобы определить, что происходит. C # 6 поддерживает фильтры исключений, поэтому будет работать следующее:

try { … }
catch (Exception e) when (MyFilter(e))
{
    …
}

Метод MyFilter может выглядеть примерно так:

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

В качестве альтернативы, все это можно сделать встроенным (правая часть оператора when просто должна быть логическим выражением).

try { … }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    …
}

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

Вы можете загрузить Visual Studio 2015 , чтобы проверить это.

Если вы хотите продолжить использование Visual Studio 2013, вы можете установить следующий пакет nuget:

Установочный пакет Microsoft.Net.Compilers

На момент написания, это будет включать поддержку C # 6.

Ссылка на этот пакет приведет к созданию проекта с использованием конкретная версия компиляторов C # и Visual Basic, содержащихся в пакет, в отличие от любой версии установленной системы.

185 голосов
/ 26 сентября 2008

К сожалению, не в C #, поскольку для этого вам понадобится фильтр исключений, а C # не предоставляет эту возможность MSIL. VB.NET имеет такую ​​возможность, например,

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

Что вы можете сделать, это использовать анонимную функцию для инкапсуляции вашего кода ошибки, а затем вызывать его в этих определенных блоках перехвата:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
129 голосов
/ 13 апреля 2013

Ради полноты, начиная с .NET 4.0 код можно переписать как:

Guid.TryParse(queryString["web"], out WebId);

TryParse никогда не генерирует исключения и возвращает false, если формат неверный, устанавливая WebId в Guid.Empty.


Поскольку C # 7 , вы можете избежать ввода переменной в отдельной строке:

Guid.TryParse(queryString["web"], out Guid webId);

Вы также можете создать методы для анализа возвращаемых кортежей, которые еще не доступны в .NET Framework начиная с версии 4.6:

(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

И используйте их так:

WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;

Следующее бесполезное обновление этого бесполезного ответа происходит, когда деконструкция out-параметров реализована в C # 12.:)

71 голосов
/ 01 апреля 2015

Если вы можете обновить свое приложение до C # 6, вам повезет. В новой версии C # реализованы фильтры исключений. Таким образом, вы можете написать это:

catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
    WebId = Guid.Empty;
}

Некоторые люди думают, что этот код такой же, как

catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

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

См. обсуждение этого на CodePlex . И пример, показывающий разницу .

58 голосов
/ 11 июля 2018

Фильтры исключений теперь доступны в c # 6+. Вы можете сделать

try
{
       WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
     WebId = Guid.Empty;
}

В C # 7.0+ вы можете комбинировать это и с сопоставлением с шаблоном

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae )
{
   //do something with members of ae, say ae.InnerExceptions
}
31 голосов
/ 07 октября 2015

Если вы не хотите использовать оператор if в пределах catch, в C# 6.0, вы можете использовать Exception Filters синтаксис , который уже поддерживается CLR в предварительных версиях, но существовал только в VB.NET / MSIL:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Этот код будет ловить Exception только тогда, когда это InvalidDataException или ArgumentNullException.

На самом деле, вы можете поместить в это условие when практически любое условие:

static int a = 8;

...

catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
    Console.WriteLine("Catch");
}

Обратите внимание, что в отличие от оператора if внутри области действия catch, Exception Filters не может выбросить Exceptions, и когда они это делают, или когда условие не true, следующее catch условие будет оценено вместо:

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Вывод: общий улов.

При наличии более одного true Exception Filter - будет принят первый:

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Вывод: Catch.

И, как вы можете видеть в MSIL, код не переводится в if операторы, но в Filters, и Exceptions нельзя выбросить из областей, отмеченных Filter 1 и Filter 2 но вместо этого произойдет сбой фильтра, выбрасывающего Exception, также последнее значение сравнения, помещенное в стек до того, как команда endfilter определит успех / сбой фильтра (Catch 1 XOR Catch 2 будет выполняться соответственно):

Exception Filters MSIL

Также, в частности, Guid имеет метод Guid.TryParse.

18 голосов
/ 05 января 2018

С помощью C # 7 можно улучшить ответ от Михаэля Стума , сохранив читаемость оператора switch:

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
18 голосов
/ 30 июля 2010

Принятый ответ кажется приемлемым, за исключением того, что CodeAnalysis / FxCop будет жаловаться на тот факт, что он перехватывает общий тип исключения.

Кроме того, кажется, что оператор is может немного ухудшить производительность.

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

Во всяком случае, вот что я бы сделал:

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...