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

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

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

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

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

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

Ответ 1

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

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

    throw;
}

Ответ 2

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

Ответ 3

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

Ответ 4

Не в С#, к сожалению, поскольку для этого вам нужен фильтр исключений, а С# не раскрывает эту функцию 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();
}

Ответ 5

Для полноты, поскольку .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.:)

Ответ 6

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

Ответ 7

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

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

В С# 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..? 
}

Ответ 8

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

Исключительные фильтры MSIL

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

Ответ 9

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

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

Ответ 10

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

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

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

Ответ 11

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

Ответ 12

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

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
}

Ответ 13

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

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
}

Ответ 14

@Micheal

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

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

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

Ответ 15

Как насчет

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

Ответ 16

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

Ответ 17

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

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

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

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

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

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

Ответ 18

Обновление 2015-12-15: см. fooobar.com/questions/687/... для С# 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*/
            }
        }
    }
}

Ответ 19

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

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

// 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 и ваши отлаживающие нечеткие исключения.

Ответ 20

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

Позвольте мне разбить ваш вопрос на 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);
}

Ответ 21

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

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

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

Ответ 22

Хотел добавить мой короткий ответ на этот уже длинный поток. Что-то, о чем не упоминалось, это порядок приоритета операторов 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:

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

Ответ 23

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

Например:

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

// ...

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

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

Ответ 24

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

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

Ответ 25

catch(ArithmeticException | ArrayIndexOutOfBounds | RunTimeException | Exception ex)  {
    ex.printStackTrace();
}

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

Ответ 26

Здесь стоит упомянуть. Вы можете ответить на несколько комбинаций (ошибка исключения и 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
} 

Ответ 27

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

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

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

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

Ответ 28

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

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

       WebId = Guid.Empty;
   }

Ответ 29

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

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

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

Ответ 30

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