Поймать несколько исключений одновременно?

1795

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

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

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

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

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

  • 5
    Если вы используете .net 4 и выше, я предпочитаю использовать aggregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
  • 1
    Bepenfriends - поскольку System.Guid не генерирует AggregateException , было бы здорово, если бы вы (или кто-то другой ) могли опубликовать ответ, показывающий, как вы бы включили его в AggregateException и т. Д.
Показать ещё 5 комментариев
Теги:
exception
exception-handling

29 ответов

1946
Лучший ответ

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

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

    throw;
}
  • 3
    не должно быть: else {throw ex; }?
  • 52
    К сожалению, FxCop (то есть - Visual Studio Code Analysis) не нравится, когда вы ловите Exception.
Показать ещё 17 комментариев
399

EDIT: Я согласен с другими, которые говорят, что с С# 6.0 фильтры исключений теперь отлично подходят: catch (Exception ex) when (ex is ... || ex is ... )

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

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

ORIGINAL:

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

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

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 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;
}
  • 28
    Когда я впервые наткнулся на этот вопрос, я был во всем принятом ответе. Круто, я могу просто поймать все Exception и проверить тип. Я думал, что это очистило код, но что-то заставляло меня возвращаться к вопросу, и я фактически читал другие ответы на вопрос. Я жевал это некоторое время, но я должен согласиться с вами. Более удобочитаемым и понятным является использование функции, чтобы высушить ваш код, чем перехватывать все, проверять сравнение типов по списку, переносу кода и выбрасыванию. Спасибо за опоздание и предоставление альтернативного и вменяемого (IMO) варианта. +1.
  • 8
    Использование функции обработки ошибок не сработает, если вы захотите включить команду throw; , Вы должны будете повторить эту строку кода в каждом блоке catch (очевидно, это не конец света, но стоит упомянуть, поскольку это код, который нужно будет повторять).
Показать ещё 7 комментариев
279

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

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

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

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

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

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

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

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

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

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

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

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

  • 3
    Терпеливо жду официального релиза 6 ... Мне бы хотелось, чтобы это получило чек, когда это произойдет.
  • 0
    @RubberDuck Я умираю за нулевой оператор распространения из C # 6. Пытаюсь убедить остальную часть моей команды, что риск нестабильного языка / компилятора того стоит. Множество мелких улучшений с огромным влиянием. Что касается того, чтобы быть отмеченным как ответ, не важно, пока люди понимают, что это будет / возможно, я счастлив.
Показать ещё 2 комментария
184

Не в С#, к сожалению, поскольку для этого вам нужен фильтр исключений, а С# не раскрывает эту функцию 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();
}
  • 26
    Интересная идея и еще один пример того, что VB.net иногда имеет некоторые интересные преимущества перед C #
  • 43
    @MichaelStum с таким синтаксисом я вряд ли бы назвать это интересно вообще ... Дрожь
Показать ещё 5 комментариев
120

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

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

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


Так как С# 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-parameters реализована в С# 12.:)

  • 18
    Точно - кратко, и вы полностью обойдете снижение производительности обработки исключений, плохую форму преднамеренного использования исключений для управления потоком программ и мягкую направленность распространения вашей логики преобразования, немного здесь и немного там ,
  • 9
    Я знаю, что вы имели в виду, но, конечно, Guid.TryParse никогда не возвращает Guid.Empty . Если строка имеет неправильный формат, она устанавливает выходной параметр result в Guid.Empty , но возвращает false . Я упоминаю об этом, потому что я видел код, который делает вещи в стиле Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ } , что обычно неверно, если s может быть строковым представлением Guid.Empty .
Показать ещё 5 комментариев
67

Если вы можете обновить приложение до С# 6, вам повезло. Новая версия С# реализовала фильтры исключений. Поэтому вы можете написать это:

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

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

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

  • 4
    Бросок без исключения сохраняет стек, но «throw ex» перезапишет его.
29

Если вы не хотите использовать оператор 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 будет выполняться соответственно):

Изображение 945

Кроме того, в частности Guid имеет метод Guid.TryParse.

  • 0
    +1 за показ нескольких фильтров и объяснение того, что происходит при использовании нескольких фильтров.
25

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

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

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

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

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}
  • 18
    Но имейте в виду, что вы не можете сбросить исключение, не потеряв трассировку стека, если вы сделаете это так. (См. Комментарий Майкла Стума к принятому ответу)
  • 2
    Этот шаблон можно улучшить, сохранив исключение (извините за плохое форматирование - я не могу понять, как поместить код в комментарии): Exception ex = null; try {// что-то} catch (FormatException e) {ex = e; } catch (OverflowException e) {ex = e; } if (ex! = null) {// что-то еще и иметь дело с ex}
Показать ещё 4 комментария
18

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

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
}
  • 0
    -1 Это выполнит WebId = Guid.Emtpy в случае, если не было WebId = Guid.Emtpy исключение.
  • 4
    @sepster Я думаю, что выражение возврата после "// что-то" подразумевается здесь. Мне не очень нравится решение, но это конструктивный вариант в обсуждении. +1, чтобы отменить ваш downvote :-)
Показать ещё 1 комментарий
17

в С# 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");
 }
17

@Micheal

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

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

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

  • 20
    Почему бы просто не использовать ключевое слово "is"?
  • 0
    Если я правильно помню, «is» - это приведение во время выполнения, тогда как typeof - это сравнение типов компиляции. Но я могу ошибаться.
Показать ещё 14 комментариев
16

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

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

    // Handle exception
}

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

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

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

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

    // Handle exception
}

Edit: Фильтрация в С# 6.0 сделает синтаксис немного более чистым и будет содержать количество других преимущества над любым текущим решением. (в первую очередь, оставить стек невредимым)

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

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
  • 2
    +1, это лучший ответ. Это лучше, чем верхний ответ в основном потому, что return нет, хотя инвертирование условия также немного лучше.
  • 0
    Я даже не думал об этом. Хороший улов, я добавлю его в список.
14

С С# 7 ответ от Michael Stum может быть улучшен, сохраняя читаемость оператора switch:

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
  • 2
    Это должен быть принятый ответ по состоянию на 2018 г. ИМХО.
  • 1
    Ответ Мэтта J, when он гораздо элегантнее / уместнее, чем переключатель.
Показать ещё 3 комментария
13

Как насчет

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
  • 0
    Это работает, только если Catch-код может быть полностью перемещен в Try-Block. Но код обработки изображений, когда вы выполняете несколько манипуляций с объектом, а одна посередине, терпит неудачу, и вы хотите «сбросить» объект.
  • 4
    В этом случае я бы добавил функцию сброса и вызвал бы ее из нескольких блоков catch.
12

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

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

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

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

(В принципе, предоставляет еще одну пустую перезагрузку Catch, которая возвращает себя)

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

  • 1
    Одним из возможных преимуществ этого подхода является то, что существует семантическая разница между перехватом и повторным выбросом исключения по сравнению с не перехватом его; в некоторых случаях код должен действовать на исключение, не перехватывая его. Такое возможно в vb.net, но не в C #, если только не используется оболочка, написанная на vb.net и вызываемая из C #.
  • 1
    Как действует исключение, не уловив его? Я не до конца понимаю вас.
Показать ещё 3 комментария
12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
10

Обновление 2015-12-15: см. https://stackoverflow.com/questions/136035/catch-multiple-exceptions-at-once для С# 6. Это более чистый и теперь стандартный на языке.

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

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

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

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 Unit

Соответствие поведения для типов 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*/
            }
        }
    }
}
  • 1
    Улучшение языка не является "более элегантным". Во многих местах это на самом деле создало ад обслуживания. Спустя годы многие программисты не гордятся тем, какого монстра они создали. Это не то, что вы привыкли читать. Это может вызвать "да?" эффект, или даже серьезные "WTFs". Это иногда сбивает с толку. Единственное, что он делает, - это усложняет понимание кода для тех, кому нужно иметь дело с ним позже в процессе обслуживания - только потому, что один программист пытался быть «умным». За эти годы я узнал, что эти «умные» решения редко бывают хорошими.
  • 1
    или в нескольких словах: придерживайтесь возможностей, которые предоставляет родной язык. не пытайтесь переопределить семантику языка только потому, что он вам не нравится. Ваши коллеги (и, возможно, будущие я) будут вам благодарны, если честно.
Показать ещё 3 комментария
7

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

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

// 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.

Чтобы заставить его работать на С#, нам нужна временная переменная с правильным базовым типом "Исключение" . Чтобы управлять потоком кода, мы можем добавить некоторые ветки. Здесь:

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

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

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

Итак, заключаем:

  • Если мы не хотим повторять бросок, мы можем подумать об улавливании правильных исключений и их временном хранении.
  • Если обработчик прост и мы хотим повторно использовать код, лучшим решением является, возможно, введение вспомогательной функции.
  • Если мы хотим повторить бросок, у нас нет выбора, кроме как поместить код в обработчик catch Exception, который нарушит FxCop и ваши отлаживающие нечеткие исключения.
6

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

Позвольте мне разбить ваш вопрос на 2 вопроса. Первый,

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

Короче говоря, нет.

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

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

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

  • Инициализировать WebId до значения возврата.
  • Построить новый указатель во временной переменной.
  • Установите 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.

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

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}
  • 0
    Это хорошее «экологическое кодирование», т.е. вы заранее обдумываете свой код и объем данных и не допускаете утечки половины обработанных значений. Хорошо будет следовать этой схеме, спасибо Джеффри!
6

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

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

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 */ }

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

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

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}
  • 2
    «Интересно, почему никто не заметил это дублирование кода». - что? Весь смысл вопроса состоит в том, чтобы устранить дублирование кода.
4

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

Например:

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

// ...

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

Просто как бы я это сделал, пытаясь найти простое красивое узоры

4

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

Например, если вы используете исключение "catch-all" как Exception, оно будет преследовать все остальные команды catch, и вы, очевидно, получите ошибки компилятора, однако, если вы отмените заказ, вы можете связать свой улов (бит анти-шаблона, я думаю), вы можете поместить в конец все Исключение, и это будет захват любых исключений, которые не удовлетворяли бы выше в вашей попытке. блок 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:

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

3

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

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
  • 7
    -1 голос, +5 WTF :-) Это не должно было быть помечено как ответ, но это он щедрый.
  • 1
    Не имеет значения, насколько просто мы могли бы это сделать. Но он не сидел без дела и высказал свое мнение, чтобы решить это. Очень ценю.
Показать ещё 3 комментария
0

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

Я столкнулся с ситуационным сценарием при попытке бросить объект управления в datagrid с любым содержимым как 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
} 
0
catch(ArithmeticException | ArrayIndexOutOfBounds | RunTimeException | Exception ex)  {
    ex.printStackTrace();
}

Вы должны ввести дополнительные особые исключения.. i.e сначала подклассы, а затем суперклассы...

-2

Маленький умный твистер, возможно, не для реальной жизни, размещая здесь только ради другого варианта и видя, насколько уродливый он

   catch (Exception ex)            
   {                
       if (ex is FormatException ||
           ex is OverflowException) 
       {} else throw;

       WebId = Guid.Empty;
   }
-13

Просто вызовите try и catch дважды.

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

Просто Простой!!

  • 1
    гм. это побеждает цель вопроса. Он задает этот вопрос, чтобы избавиться от дубликата кода. этот ответ добавляет больше повторяющегося кода.
-19

В С# 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");
    }
}
  • 12
    В этом примере не показано использование фильтров исключений.
  • 0
    Это стандартный способ фильтрации исключений в C # 6.0
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню