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


Answers

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

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

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

ОРИГИНАЛ:

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

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

Фаза обслуживания для всех, кто может быть относительно новичком в программировании, будет составлять 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;
}
Question

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

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

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

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

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




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

См. roslyn.codeplex.com/discussions/541301 . И пример, показывающий разницу .




Just call the try and catch twice.

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

It is just that Simple!!




Не в 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();
}



So you´re repeating lots of code within every exception-switch? Sounds like extracting a method would be god idea, doesn´t it?

So your code comes down to this:

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

I wonder why no-one noticed that code-duplication.

From C#6 you furthermore have the exception-filters as already mentioned by others. So you can modify the code above to this:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}



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

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

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}



@Micheal

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

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

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




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

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

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

Two Sample NUnit Unit Tests

Matching behaviour for Exception types is exact (ie. A child IS NOT a match for any of its parent types).

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(Exception)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}



Wanted to added my short answer to this already long thread. Something that hasn't been mentioned is the order of precedence of the catch statements, more specifically you need to be aware of the scope of each type of exception you are trying to catch.

For example if you use a "catch-all" exception as Exception it will preceed all other catch statements and you will obviously get compiler errors however if you reverse the order you can chain up your catch statements (bit of an anti-pattern I think) you can put the catch-all Exception type at the bottom and this will be capture any exceptions that didn't cater for higher up in your try..catch block:

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

I highly recommend folks review this MSDN document:

Exception Hierarchy




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

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

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

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

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

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




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

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
}



This is a classic problem every C# developer faces eventually.

Let me break your question into 2 questions. The first,

Can I catch multiple exceptions at once?

In short, no.

Which leads to the next question,

How do I avoid writing duplicate code given that I can't catch multiple exception types in the same catch() block?

Given your specific sample, where the fall-back value is cheap to construct, I like to follow these steps:

  1. Initialize WebId to the fall-back value.
  2. Construct a new Guid in a temporary variable.
  3. Set WebId to the fully constructed temporary variable. Make this the final statement of the try{} block.

So the code looks like:

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

If any exception is thrown, then WebId is never set to the half-constructed value, and remains Guid.Empty.

If constructing the fall-back value is expensive, and resetting a value is much cheaper, then I would move the reset code into its own function:

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



Related