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



13 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;
}
генерация исключений c#

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

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

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

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

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




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



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




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

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



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

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
}



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



Как насчет

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 которая возвращает себя)

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




Обновление 2015-12-15: см. https://.com/a/22864936/1718702 для C # 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 Unit Tests

Соответствующее поведение для 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(Exception)))
                    throw;

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



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

Таким образом, ваш код сводится к следующему:

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 вы также имеете exception-filters как уже упоминалось другими. Таким образом, вы можете изменить код выше:

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



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

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



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

Например:

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

// ...

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

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




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

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}



Related