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

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

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

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

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

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

26 ответов


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

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

    throw;
}

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

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

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

оригинал:

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

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

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 лает на вас, и поэтому вы должны и добавьте атрибут к вашему коду, который имеет точно zip, чтобы сделать с запущенной программой, и только там, чтобы сказать FxCop игнорировать проблему, которая в 99,9% случаев полностью корректна в помечении. И, извините, я могу ошибаться, но разве этот атрибут "игнорировать" не компилируется в ваше приложение?

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

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

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

просто говорю...

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

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

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.Сеть.Компиляторы

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


С C# 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-параметров реализована в 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. И пример, показывающий разницу.


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

Выход: Поймать.

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

Exception Filters MSIL

также, в частности,Guid имеет Guid.TryParse метод.


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

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

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

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

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
}

@Micheal

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

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

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


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

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

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: The исключения фильтрации в C# 6.0 сделает синтаксис немного чище и поставляется с ряд других преимуществ над любым текущим решением. (в частности, оставив стек невредимым)

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

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

как о

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

С C# 7 ответ от Михаил Бурчак можно улучшить, сохраняя читаемость оператора switch:

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

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

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

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

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

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

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

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

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

полный пример обработки ошибок (копировать-вставить в новую консоль app)

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, проблемы с отладчиком, uglyness)

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

если мы посмотрим на код, что мы действительно хотели бы сделать, это переадресовать звонок. Однако, согласно MS Partition II, блоки обработчика исключений IL не будут работать так, что в этом случае имеет смысл, потому что это означало бы, что объект "exception" может иметь разные типы.

или написать его в коде, мы просим компилятор сделать что-то вроде этого (ну, это не совсем правильно, но это самая близкая вещь, которую я думаю):

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

чтобы он работал в C#, нам нужна временная переменная с правильным базовым типом "исключение". Для управления потоком кода можно добавить несколько ветвей. Вот:

    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.");

и другое решение-поймать объект исключения и обработать его соответствующим образом. Самый буквальный перевод для этого, основанный на приведенном выше контексте, таков:

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 и необработанные исключения вашего отладчика.

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

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

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

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

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

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

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

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

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

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

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


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

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

это так просто!!


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