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

StackOverflow https://stackoverflow.com/questions/136035

Вопрос

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

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

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

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

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

Это было полезно?

Решение

Ловить System.Exception и включите типы

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

    throw;
}

Другие советы

РЕДАКТИРОВАТЬ: Я согласен с другими, которые говорят, что начиная с 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 лает на вас, и вам приходится также добавьте в свой код атрибут, который имеет прямое отношение к работающей программе и предназначен только для того, чтобы сообщить 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;
}

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

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

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

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

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

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

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

Вы можете скачать Визуальная Студия 2015 чтобы проверить это.

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

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

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

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

К сожалению, не в 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();
}

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

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

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

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

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

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

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae )
{
   //do something with members of ae, say ae.InnerExceptions
}

Если вы не хотите использовать 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");
}

Выход:Ловить.

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

Exception Filters MSIL

Также, конкретно Guid имеет Guid.TryParse метод.

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

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

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

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

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

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

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

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
}

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

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
}

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

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

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}

@Майкл

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

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

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

Как насчет

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

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

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

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

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

(По сути, предоставьте еще один пустой Catch перегрузка, которая возвращается сама)

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

catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}

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

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

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

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

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

// 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»)?Мы хотим, чтобы компилятор обрабатывал эту ситуацию: обратите внимание, что общим базовым типом обоих исключений является «Exception», используйте его для переменной, содержащей оба исключения, а затем обрабатывайте только два перехваченных исключения.В 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 и неперехваченных исключений вашего отладчика.

Это классическая проблема, с которой рано или поздно сталкивается каждый разработчик 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);
}

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

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

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

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

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

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

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

Например.:

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

// ...

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

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

Обратите внимание, что я нашел один способ сделать это, но это больше похоже на материал для Ежедневный черт возьми:

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

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

Я столкнулся со сценарием использования при попытке привести объект управления в сетку данных с содержимым в виде 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
} 

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

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

п.с.Я не ставил проверки на null для простоты кода, рассмотрите возможность добавления проверок параметров.

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

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

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

Это так Просто!!

В С# 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");
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top