Отловить сразу несколько исключений?

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

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

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

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

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


person Michael Stum    schedule 25.09.2008    source источник
comment
Если вы используете .net 4 и выше, я предпочитаю использовать aggregateexception msdn. microsoft.com/en-us/library/system.aggregateexception.aspx   -  person Albert Arul prakash    schedule 18.10.2013
comment
Bepenfriends - поскольку System.Guid не генерирует AggregateException, было бы здорово, если бы вы (или кто-то) могли опубликовать ответ, показывающий, как вы могли бы заключить его в AggregateException и т. Д. .   -  person weir    schedule 30.01.2014
comment
При использовании AggregateException: Создание исключения AggregateException в моем собственном коде   -  person DavidRR    schedule 14.08.2014
comment
Не рекомендуется просто перехватывать System.Exception. -и если метод может генерировать 32 типа исключений, что он делает? напишите уловку для каждого из них отдельно?   -  person Giorgi Moniava    schedule 23.05.2015
comment
Держите его таким, какой у вас есть. Переместите код в обработчик ошибок, если хотите, чтобы на каждый оператор catch оставалась только одна строка.   -  person rolls    schedule 04.03.2017
comment
Если метод выдает 32 разных типа исключений, это плохо написано. Он либо не улавливает исключения, которые делают собственные вызовы, либо слишком много делает FAR в одном методе, либо большинство / все из этих 32 должны быть одним исключением с кодом причины.   -  person Flynn1179    schedule 19.05.2017
comment
Принятый ответ устарел; см. вместо этого, поскольку он был обновлен с помощью предложения Edit вверху: stackoverflow.com/a/19329123/398630   -  person BrainSlugs83    schedule 17.05.2019
comment
Поскольку ни один из ответов, которые я прокрутил мимо, не упоминал об этом, я здесь, чтобы сказать, что ваше начальное утверждение ложно. Не рекомендуется просто ловить System.Exception, это неправильно, для этого есть очень веские причины, не рекомендуется только ловить System.Exception   -  person DreadedEntity    schedule 07.07.2021


Ответы (27)


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

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }
    
    throw;
}
person Joseph Daigle    schedule 25.09.2008
comment
Обязательное напоминание редакторам, не являющимся OP: редактирование новых ответов на обновления - это то, что у нас есть причина отказа, и ›2k пользователей не освобождаются от этого. Не обновляйте ответы других людей, чтобы они отражали обновления стандартных версий или других версий какой-либо технологии, применимой к любым произвольным ответам - вместо этого опубликуйте новый ответ (совет профессионала; в этом для вас больше репутации). Если также есть крайние возражения против ответа, вы оставляете комментарий, объясняющий проблему, и ссылку на любой ответ, который сейчас более применим. (И голосуйте за ответ, как хотите) - person Zoe; 07.07.2021

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

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

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

ОРИГИНАЛ:

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

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

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% случаев полностью корректна при пометке. И, извините, я могу ошибаться, но разве этот атрибут ignore не компилируется в ваше приложение?

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

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

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

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

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}
person Craig    schedule 12.10.2013

Как указывали другие, вы можете иметь оператор 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, содержащихся в пакете, в отличие от любой версии, установленной системой.

person Joe    schedule 04.04.2014

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

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

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

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
person Greg Beech    schedule 25.09.2008

Фильтры исключений теперь доступны в 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 &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}
person Mat J    schedule 11.07.2018
comment
Этот метод предпочтительнее не только потому, что он прост и понятен, но и не требует раскрутки стека, если условия не выполняются, что обеспечивает лучшую производительность и диагностическую информацию по сравнению с повторным выбросом. - person joe; 29.07.2019

Для полноты картины, начиная с .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;

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

person Athari    schedule 13.04.2013

Если вы можете обновить свое приложение до 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 Больше недоступно. И пример, показывающий разницу.

person Maniero    schedule 01.04.2015
comment
Throw без исключения сохраняет стек, но throw ex перезапишет его. - person Ivan; 12.04.2017

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

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");
}

Выход: Поймать.

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

Фильтры исключений MSIL

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

person Tamir Vered    schedule 07.10.2015

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

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

Благодаря комментарию Orace это можно упростить с помощью C # 8, опустив переменную discard:

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException:
        case OverflowException:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
} 

И с C # 8 в качестве выражения переключателя:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}

Как указал Nechemia-hoffmann. Последний пример приведет к потере трассировки стека.

person Fabian    schedule 05.01.2018
comment
Не потеряете ли вы трассировку стека, если throw ex? - person Nechemia Hoffmann; 06.01.2021
comment
Да, в примере выражения переключателя (2-й пример) вы теряете трассировку стека. Спасибо что подметил это. (Чтобы быть ясным: вы не потеряете его в первом примере) - person Fabian; 08.01.2021
comment
Для первого блока кода _ больше не нужны в C # 8 - person Orace; 26.05.2021
comment
@Orace: Спасибо за комментарий. Я обновил ответ соответственно. - person Fabian; 27.05.2021

Обновление для C # 9

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

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception e) when (e is FormatException or OverflowException)
{
    WebId = Guid.Empty;
}
person Nechemia Hoffmann    schedule 06.01.2021

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

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

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}
person Matt    schedule 30.07.2010

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

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }
person SHM    schedule 04.10.2015

Это вариант ответа Мэтта (я чувствую, что это немного чище) ... используйте метод:

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

Будут выброшены любые другие исключения, а код WebId = Guid.Empty; не сработает. Если вы не хотите, чтобы другие исключения приводили к сбою вашей программы, просто добавьте это ПОСЛЕ двух других перехватов:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}
person bsara    schedule 31.08.2012

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

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Инвертирование выражения дает несколько преимуществ:

  • Заявление о возврате не требуется
  • Код не вложен
  • Нет риска забыть операторы throw или return, которые в решении Джозефа отделены от выражения.

Его даже можно сжать до одной строки (хотя и не очень красиво)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

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

Вот как будет выглядеть та же проблема с использованием синтаксиса C # 6.0:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
person Stefan T    schedule 08.12.2014

@Micheal

Немного переработанная версия вашего кода:

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

Сравнение строк уродливо и медленно.

person FlySwat    schedule 25.09.2008

Как насчет

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
person Maurice    schedule 25.09.2008
comment
Это работает только в том случае, если Catch-Code можно полностью переместить в блок попытки. Но код визуализации, в котором вы выполняете несколько манипуляций с объектом, а одно из них в середине дает сбой, и вы хотите сбросить объект. - person Michael Stum; 26.09.2008
comment
В этом случае я бы добавил функцию сброса и вызвал ее из нескольких блоков catch. - person Maurice; 26.09.2008
comment
OP запросил одновременную перехват нескольких исключений. Вы ловите их в разных блоках - person avivgood2; 25.09.2020

Внимание и предупреждение: Еще другой, функциональный стиль.

То, что находится в ссылке, не дает прямого ответа на ваш вопрос, но тривиально расширить ее, чтобы она выглядела так:

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(Обычно предоставляют еще одну пустую Catch перегрузку, которая возвращает сама себя)

Главный вопрос - почему. Не думаю, что здесь стоимость перевешивает выигрыш :)

person nawfal    schedule 18.05.2013

Обновление 2015-12-15: см. https://stackoverflow.com/a/22864936/1718702 для C # 6. Это более чистый и теперь стандартный для языка.

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

У меня уже было это расширение в моей библиотеке, изначально написанное для других целей, но оно отлично работало для type проверки исключений. Плюс, imho, это выглядит чище, чем кучка || утверждений. Кроме того, в отличие от принятого ответа, я предпочитаю явную обработку исключений, поэтому ex is ... имел нежелательное поведение, поскольку производные классы назначаются родительским типам).

Использование

if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

Расширение IsAnyOf.cs (см. пример полной обработки ошибок для зависимостей)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

Пример полной обработки ошибок (копирование и вставка в новое консольное приложение)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

Два образца модульных тестов NUnit

Поведение соответствия для Exception типов является точным (т. Е. Дочерний элемент НЕ совпадает ни с одним из своих родительских типов).

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}
person HodlDwon    schedule 27.11.2013
comment
Улучшение языка не более элегантно. Во многих местах это фактически создало ад технического обслуживания. Спустя годы многие программисты уже не гордятся тем, какого монстра они создали. Это не то, что вы привыкли читать. Это может вызвать а? эффект, или даже серьезные WTF. Иногда это сбивает с толку. Единственное, что он делает, - это усложняет понимание кода для тех, кому придется иметь дело с ним позже при обслуживании - только потому, что один-единственный программист пытался быть умным. С годами я понял, что эти умные решения редко бывают хорошими. - person Kaii; 10.10.2014

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

Итак, что мы действительно хотели бы сделать, это то, что не компилируется, например:

// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException) 
    {
        // ... handle
    }

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

Так почему же этот код не компилируется - и как мы можем его так взломать?

Если мы посмотрим на код, то действительно хотели бы переадресовать вызов. Однако, согласно MS Partition II, блоки обработчика исключений IL не будут работать таким образом, что в данном случае имеет смысл, поскольку это будет означать, что объект «исключение» может иметь разные типы.

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

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

Причина, по которой это не скомпилируется, довольно очевидна: какой тип и значение будет иметь объект '$ exception' (которые здесь хранятся в переменных 'e')? Мы хотим, чтобы компилятор обрабатывал это, чтобы отметить, что общим базовым типом обоих исключений является «Исключение», используйте его, чтобы переменная содержала оба исключения, а затем обрабатывала только два перехваченных исключения. В IL это реализовано в виде «фильтра», доступного в VB.Net.

Чтобы он работал на C #, нам нужна временная переменная с правильным базовым типом Exception. Чтобы контролировать поток кода, мы можем добавить несколько веток. Поехали:

    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");

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

Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}

Остается только «переброс». Чтобы это работало, нам нужно иметь возможность выполнять обработку внутри блока «catch» - и единственный способ сделать это - перехватить объект «Exception».

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

private static bool Handle(Exception e)
{
    Console.WriteLine("Handle the exception here :-)");
    return true; // false will re-throw;
}

public static void Main()
{
    try
    {
        throw new OutOfMemoryException();
    }
    catch (ArgumentException e)
    {
        if (!Handle(e)) { throw; }
    }
    catch (IndexOutOfRangeException e)
    {
        if (!Handle(e)) { throw; }
    }

    Console.WriteLine("We're done with the exception handling.");

И другое решение - перехватить объект Exception и обработать его соответствующим образом. Самый дословный перевод этого, исходя из приведенного выше контекста, таков:

try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else 
    {
        throw;
    }
}

Итак, в заключение:

  • Если мы не хотим повторно генерировать вызовы, мы можем рассмотреть возможность перехвата правильных исключений и их временного хранения.
  • Если обработчик простой, и мы хотим повторно использовать код, лучшим решением, вероятно, будет введение вспомогательной функции.
  • Если мы хотим повторно выбросить, у нас нет другого выбора, кроме как поместить код в обработчик исключения «Исключение», который нарушит работу FxCop и неперехваченных исключений вашего отладчика.
person atlaste    schedule 21.10.2014

Это классическая проблема, с которой рано или поздно сталкивается каждый разработчик C #.

Разрешите разбить ваш вопрос на 2 вопроса. Первое,

Могу ли я поймать сразу несколько исключений?

Короче нет.

Это приводит к следующему вопросу:

Как мне избежать написания повторяющегося кода, учитывая, что я не могу перехватить несколько типов исключений в одном блоке catch ()?

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

  1. Инициализируйте WebId для резервного значения.
  2. Создайте новый Guid во временной переменной.
  3. Установите WebId на полностью созданную временную переменную. Сделайте это последним оператором блока try {}.

Итак, код выглядит так:

try
{
    WebId = Guid.Empty;
    Guid newGuid = new Guid(queryString["web"]);
    // More initialization code goes here like 
    // newGuid.x = y;
    WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}

Если генерируется какое-либо исключение, тогда WebId никогда не устанавливается равным наполовину сконструированному значению и остается Guid.Empty.

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

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}
person Jeffrey Rennie    schedule 15.11.2017
comment
Это хорошее, экологичное кодирование, то есть вы заранее думаете о своем коде и объеме данных и следите за тем, чтобы не было утечки полуобработанных значений. Приятно следовать этому образцу, спасибо Джеффри! - person Trevor; 05.03.2018

Обратите внимание, что я нашел один способ сделать это, но он больше похож на материал для The Daily WTF :

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
person Community    schedule 25.09.2008
comment
-1 голос, +5 WTF :-) Это не должно было быть отмечено как ответ, но это опасно. - person Aaron; 26.09.2008
comment
Неважно, насколько просто мы могли это сделать. Но он не сидел сложа руки и высказал свою точку зрения, как ее решить. Очень ценю. - person Maxymus; 21.01.2016
comment
На самом деле не делайте этого, используйте фильтры исключений в С # 6 или любой из других ответов - я поставил это здесь специально, поскольку это один из способов, но это плохо, и я хочу сделать что-то лучше. - person Michael Stum; 21.01.2016
comment
ПОЧЕМУ это плохо? Я был озадачен, что вы не можете напрямую использовать исключение в операторе switch. - person MKesper; 08.06.2016
comment
@MKesper Я вижу несколько причин, по которым это плохо. Это требует записи полных имен классов в виде строковых литералов, что уязвимо для опечаток, от которых компилятор не может вас спасти. (Это важно, поскольку во многих магазинах случаи ошибок менее тщательно проверяются, и поэтому вероятность того, что в них будут пропущены тривиальные ошибки, будет выше.) Также не удастся сопоставить исключение, которое является подклассом одного из них. указанных случаев. И из-за того, что они являются строками, кейсы будут пропущены такими инструментами, как VS Find All References - уместно, если вы хотите добавить шаг очистки везде, где обнаружено конкретное исключение. - person Mark Amery; 24.10.2017

Значит, вы повторяете много кода в каждом переключателе исключения? Похоже, извлечение метода было бы идеей бога, не так ли?

Итак, ваш код сводится к следующему:

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

Интересно, почему никто не заметил этого дублирования кода.

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

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}
person HimBromBeere    schedule 22.09.2016
comment
Интересно, почему никто не заметил это дублирование кода. - а, что? Вся суть вопроса состоит в том, чтобы исключить дублирование кода. - person Mark Amery; 24.10.2017

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

Например, если вы используете универсальное исключение как Исключение, оно будет предшествовать всем другим операторам catch, и вы, очевидно, получите ошибки компилятора, однако, если вы измените порядок, вы можете связать свои операторы catch (бит я думаю, вы можете поместить универсальный тип Exception внизу, и он будет захватывать любые исключения, которые не обслуживаются выше в вашем блоке try..catch:

            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }

Я настоятельно рекомендую людям просмотреть этот документ MSDN:

Иерархия исключений

person Trevor    schedule 16.04.2017

Может быть, попытаться сделать свой код простым, например, поместить общий код в метод, как если бы вы поступили в любой другой части кода, которая не находится внутри предложения catch?

E.g.:

try
{
    // ...
}
catch (FormatException)
{
    DoSomething();
}
catch (OverflowException)
{
    DoSomething();
}

// ...

private void DoSomething()
{
    // ...
}

Как бы я это сделал, пытаясь найти шаблон просто - красиво

person Żubrówka    schedule 23.01.2018

Здесь стоит упомянуть. Вы можете ответить на несколько комбинаций (Exception error и exception.message).

Я столкнулся со сценарием использования при попытке привести объект управления в сетку данных с любым содержимым как TextBox, TextBlock или CheckBox. В этом случае возвращенное исключение было таким же, но сообщение изменилось.

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 
person George    schedule 07.12.2018

Хочу предложить кратчайший ответ (еще один функциональный стиль):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

Для этого вам нужно создать несколько перегрузок метода Catch, аналогичных System.Action:

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

и так далее сколько угодно. Но вам нужно сделать это один раз, и вы можете использовать его во всех своих проектах (или, если вы создали пакет nuget, мы тоже могли бы его использовать).

И реализация CatchMany:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

p.s. Я не ставил нулевые проверки для простоты кода, подумайте о добавлении проверки параметров.

p.s.2 Если вы хотите вернуть значение из улова, необходимо использовать те же методы Catch, но с возвратами и Func вместо Action в параметрах.

person Evgeny Gorbovoy    schedule 27.05.2019

В C # 6.0 фильтры исключений - это улучшения для обработки исключений.

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}
person Kashif    schedule 20.05.2015
comment
В этом примере не показано использование фильтров исключений. - person user247702; 27.05.2015
comment
Это стандартный способ фильтрации исключений в c # 6.0. - person Kashif; 27.05.2015
comment
Посмотрите еще раз, что такое фильтры исключений. В своем примере вы не используете фильтр исключений. В этом ответе, опубликованном за год до вашего, есть подходящий пример. - person user247702; 27.05.2015
comment
Пример фильтрации исключений: catch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; } - person saluce; 13.11.2015