Не рекомендуется просто ловить System.Exception. Вместо этого следует перехватывать только «известные» исключения.
Иногда это приводит к ненужному повторяющемуся коду, например:
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
Интересно: есть ли способ поймать оба исключения и вызвать вызов WebId = Guid.Empty только один раз?
Данный пример довольно простой, так как это всего лишь GUID. Но представьте себе код, в котором вы изменяете объект несколько раз, и если одна из манипуляций ожидаемо терпит неудачу, вы хотите «сбросить» object. Однако, если возникнет непредвиденное исключение, я все равно хочу подбросить его выше.
Bepenfriends - поскольку System.Guid не генерирует AggregateException, было бы здорово, если бы вы (или кто-то) могли опубликовать ответ, показывающий, как вы бы заключили его в AggregateException и т. д.
При использовании AggregateException: Создание исключения AggregateException в моем собственном коде
«Не рекомендуется просто перехватывать System.Exception». -и если метод может генерировать 32 типа исключений, что он делает? написать уловку для каждого из них отдельно?
Держите его таким, какой у вас есть. Переместите код в обработчик ошибок, если хотите, чтобы на каждый оператор catch оставалась только одна строка.
Если метод выдает 32 разных типа исключений, это плохо написано. Он либо не улавливает исключения, которые делают собственные вызовы, либо слишком много делает FAR в одном методе, либо большинство / все из этих 32 должны быть одним исключением с кодом причины.
Принятый ответ устарел; см. вместо этого, поскольку он был обновлен с помощью предложения Edit вверху: stackoverflow.com/a/19329123/398630





Обратите внимание, что я нашел один способ сделать это, но он больше похож на материал для Ежедневный WTF:
catch (Exception ex)
{
switch (ex.GetType().Name)
{
case "System.FormatException":
case "System.OverflowException":
WebId = Guid.Empty;
break;
default:
throw;
}
}
-1 голос, +5 WTF :-) Это не должно было быть отмечено как ответ, но это опасно.
Неважно, насколько просто мы могли это сделать. Но он не сидел сложа руки и высказал свою точку зрения, как ее решить. Очень ценю.
На самом деле не делайте этого, используйте фильтры исключений в C# 6 или любые другие ответы - я специально указал это здесь как «Это один способ, но это плохо, и я хочу сделать что-то лучше».
ПОЧЕМУ это плохо? Я был озадачен, что вы не можете напрямую использовать исключение в операторе switch.
@MKesper Я вижу несколько причин, по которым это плохо. Это требует записи полных имен классов в виде строковых литералов, что уязвимо для опечаток, от которых компилятор не может вас спасти. (Это важно, поскольку во многих магазинах случаи ошибок менее тщательно проверяются, и поэтому вероятность того, что в них будут пропущены тривиальные ошибки, выше.) Также не удастся сопоставить исключение, которое является подкласс одного из указанных случаев. И из-за того, что они являются строками, кейсы будут пропущены такими инструментами, как VS «Найти все ссылки» - уместно, если вы хотите добавить шаг очистки везде, где обнаружено конкретное исключение.
Как насчет
try
{
WebId = Guid.Empty;
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
Это работает, только если Catch-Code можно полностью переместить в блок попытки. Но код визуализации, в котором вы производите несколько манипуляций с объектом, а одно из них в середине дает сбой, и вы хотите «сбросить» объект.
В этом случае я бы добавил функцию сброса и вызвал ее из нескольких блоков catch.
OP запросил одновременную перехват нескольких исключений. Вы ловите их в разных блоках
Лови System.Exception и включай типы
catch (Exception ex)
{
if (ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
return;
}
throw;
}
@Micheal
Немного переработанная версия вашего кода:
catch (Exception ex)
{
Type exType = ex.GetType();
if (exType == typeof(System.FormatException) ||
exType == typeof(System.OverflowException)
{
WebId = Guid.Empty;
} else {
throw;
}
}
Сравнение строк уродливо и медленно.
К сожалению, не в 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();
}
catch (Exception ex)
{
if (!(
ex is FormatException ||
ex is OverflowException))
{
throw;
}
Console.WriteLine("Hello");
}
Принятый ответ кажется приемлемым, за исключением того, что 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
}
Для полноты, поскольку .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;
Следующее бесполезное обновление этого бесполезного ответа происходит, когда в C# 12 реализована деконструкция выходных параметров :)
То, что находится в ссылке, не дает прямого ответа на ваш вопрос, но тривиально расширить ее, чтобы она выглядела так:
static void Main()
{
Action body = () => { ...your code... };
body.Catch<InvalidOperationException>()
.Catch<BadCodeException>()
.Catch<AnotherException>(ex => { ...handler... })();
}
(В основном предоставьте еще одну пустую перегрузку Catch, которая возвращается сама)
Главный вопрос к этому - Почему. Не думаю, что здесь стоимость перевешивает выигрыш :)
Обновлено: Я согласен с другими, которые говорят, что, начиная с C# 6.0, фильтры исключений теперь идеально подходят: catch (Exception ex) when (ex is ... || ex is ... )
За исключением того, что я все еще ненавижу макет из одной длинной строки и лично выложил бы код следующим образом. Я считаю, что это так же функционально, как и эстетично, поскольку я считаю, что это улучшает понимание. Некоторые могут не согласиться:
catch (Exception ex) when (
ex is ...
|| ex is ...
|| ex is ...
)
ОРИГИНАЛ:
Я знаю, что немного опаздываю на вечеринку здесь, но святой дым ...
Переходя прямо к делу, этот вид дублирует предыдущий ответ, но если вы действительно хотите выполнить общее действие для нескольких типов исключений и сохранить все это аккуратно и аккуратно в рамках одного метода, почему бы просто не использовать лямбда / closure / встроенная функция, чтобы сделать что-то вроде следующего? Я имею в виду, что велики шансы, что вы в конечном итоге поймете, что просто хотите сделать это закрытие отдельным методом, который можно использовать повсюду. Но тогда это будет очень легко сделать без фактического изменения остальной части кода структурно. Верно?
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% случаев полностью исправьте отметку. И, извините, я могу ошибаться, но разве этот атрибут "ignore" не компилируется в ваше приложение?
Может ли размещение всего теста if в одной строке сделать его более читаемым? Я так не думаю. Я имею в виду, как-то давно у меня был другой программист, который яростно утверждал, что размещение большего количества кода в одной строке заставит его «работать быстрее». Но, конечно, он был безумным бредом. Пытаться объяснить ему (с невозмутимым лицом - что было непросто), как интерпретатор или компилятор разорвет эту длинную строку на отдельные операторы с одной инструкцией на строку - по сути, идентичный результату, если бы он пошел вперед и просто сделал код читабельным, вместо того, чтобы пытаться перехитрить компилятор - на него это никак не повлияло. Но я отвлекся.
Насколько хорошо читается меньше, если вы добавите еще три типа исключений через месяц или два? (Ответ: много становится менее читаемым).
На самом деле, один из основных моментов заключается в том, что основная цель форматирования текстового исходного кода, на который мы все смотрим каждый день, - сделать это действительно, действительно очевидным для других людей, что на самом деле происходит при выполнении кода. Потому что компилятор превращает исходный код во что-то совершенно другое, и ему наплевать на ваш стиль форматирования кода. Так что все на одной линии тоже полный отстой.
Просто говорю...
// super sucks...
catch( Exception ex )
{
if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
{
// write to a log, whatever...
return;
}
throw;
}
Обновление 2015-12-15: см. https://stackoverflow.com/a/22864936/1718702 для C# 6. Это более чистый и теперь стандартный для языка.
Предназначенный для людей, которые хотят, чтобы более элегантное решение перехватил один раз и отфильтровал исключения, я использую метод расширения, как показано ниже.
У меня уже было это расширение в моей библиотеке, изначально написанное для других целей, но оно отлично работало для проверки type на исключения. Плюс, imho, это выглядит чище, чем связка утверждений ||. Кроме того, в отличие от принятого ответа, я предпочитаю явную обработку исключений, поэтому у ex is ... было нежелательное поведение, поскольку производные классы назначаются родительским типам).
использование
if (ex.GetType().IsAnyOf(
typeof(FormatException),
typeof(ArgumentException)))
{
// Handle
}
else
throw;
Расширение IsAnyOf.cs (см. Полный пример обработки ошибок для зависимостей)
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter matches at least one of the passed in comparisons.
/// </summary>
/// <typeparam name = "T"></typeparam>
/// <param name = "p_parameter">Parameter to validate.</param>
/// <param name = "p_comparisons">Values to compare against.</param>
/// <returns>True if a match is found.</returns>
/// <exception cref = "ArgumentNullException"></exception>
public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
{
// Validate
p_parameter
.CannotBeNull("p_parameter");
p_comparisons
.CannotBeNullOrEmpty("p_comparisons");
// Test for any match
foreach (var item in p_comparisons)
if (p_parameter.Equals(item))
return true;
// Return no matches found
return false;
}
}
}
Пример полной обработки ошибок (копирование и вставка в новое консольное приложение)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;
namespace IsAnyOfExceptionHandlerSample
{
class Program
{
static void Main(string[] args)
{
// High Level Error Handler (Log and Crash App)
try
{
Foo();
}
catch (OutOfMemoryException ex)
{
Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
Console.ReadKey();
}
}
static void Foo()
{
// Init
List<Action<string>> TestActions = new List<Action<string>>()
{
(key) => { throw new FormatException(); },
(key) => { throw new ArgumentException(); },
(key) => { throw new KeyNotFoundException();},
(key) => { throw new OutOfMemoryException(); },
};
// Run
foreach (var FooAction in TestActions)
{
// Mid-Level Error Handler (Appends Data for Log)
try
{
// Init
var SomeKeyPassedToFoo = "FooParam";
// Low-Level Handler (Handle/Log and Keep going)
try
{
FooAction(SomeKeyPassedToFoo);
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(FormatException),
typeof(ArgumentException)))
{
// Handle
Console.WriteLine("ex was {0}", ex.GetType().Name);
Console.ReadKey();
}
else
{
// Add some Debug info
ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
throw;
}
}
}
catch (KeyNotFoundException ex)
{
// Handle differently
Console.WriteLine(ex.Message);
int Count = 0;
if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
foreach (var Key in ex.Data.Keys)
Console.WriteLine(
"[{0}][\"{1}\" = {2}]",
Count, Key, ex.Data[Key]);
Console.ReadKey();
}
}
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter matches at least one of the passed in comparisons.
/// </summary>
/// <typeparam name = "T"></typeparam>
/// <param name = "p_parameter">Parameter to validate.</param>
/// <param name = "p_comparisons">Values to compare against.</param>
/// <returns>True if a match is found.</returns>
/// <exception cref = "ArgumentNullException"></exception>
public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
{
// Validate
p_parameter
.CannotBeNull("p_parameter");
p_comparisons
.CannotBeNullOrEmpty("p_comparisons");
// Test for any match
foreach (var item in p_comparisons)
if (p_parameter.Equals(item))
return true;
// Return no matches found
return false;
}
/// <summary>
/// Validates if any passed in parameter is equal to null.
/// </summary>
/// <param name = "p_parameters">Parameters to test for Null.</param>
/// <returns>True if one or more parameters are null.</returns>
public static bool IsAnyNull(params object[] p_parameters)
{
p_parameters
.CannotBeNullOrEmpty("p_parameters");
foreach (var item in p_parameters)
if (item == null)
return true;
return false;
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
/// </summary>
/// <param name = "p_parameter">Parameter to validate.</param>
/// <param name = "p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref = "ArgumentNullException"></exception>
public static void CannotBeNull(this object p_parameter, string p_name)
{
if (p_parameter == null)
throw
new
ArgumentNullException(
string.Format("Parameter \"{0}\" cannot be null.",
p_name), default(Exception));
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
/// </summary>
/// <typeparam name = "T"></typeparam>
/// <param name = "p_parameter">Parameter to validate.</param>
/// <param name = "p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref = "ArgumentNullException"></exception>
/// <exception cref = "ArgumentOutOfRangeException"></exception>
public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
{
if (p_parameter == null)
throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));
if (p_parameter.Count <= 0)
throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
}
/// <summary>
/// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
/// </summary>
/// <param name = "p_parameter">Parameter to validate.</param>
/// <param name = "p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref = "ArgumentException"></exception>
public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
{
if (string.IsNullOrEmpty(p_parameter))
throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
}
}
}
Два примера модульных тестов NUnit
Поведение соответствия для типов Exception является точным (т. Е. Дочерний элемент НЕ соответствует ни одному из своих родительских типов).
using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;
namespace UnitTests.Common.Fluent_Validations
{
[TestFixture]
public class IsAnyOf_Tests
{
[Test, ExpectedException(typeof(ArgumentNullException))]
public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
{
Action TestMethod = () => { throw new ArgumentNullException(); };
try
{
TestMethod();
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
typeof(FormatException),
typeof(KeyNotFoundException)))
{
// Handle expected Exceptions
return;
}
//else throw original
throw;
}
}
[Test, ExpectedException(typeof(OutOfMemoryException))]
public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
{
Action TestMethod = () => { throw new OutOfMemoryException(); };
try
{
TestMethod();
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(OutOfMemoryException),
typeof(StackOverflowException)))
throw;
/*else... Handle other exception types, typically by logging to file*/
}
}
}
}
Улучшение языка - это нет «более элегантный». Во многих местах это фактически превратило ремонтный ад. Спустя годы многие программисты уже не гордятся тем, какого монстра они создали. Это не то, что вы привыкли читать. Это может вызвать "а?" эффект, или даже серьезный "WTFs". Иногда это сбивает с толку. Единственное, что он делает, - это усложняет понимание кода для тех, кому придется иметь дело с ним позже при обслуживании - только потому, что один-единственный программист пытался быть «умным». С годами я понял, что эти «умные» решения редко бывают хорошими.
Как указывали другие, вы можете иметь инструкцию if внутри вашего блока catch, чтобы определить, что происходит. C# 6 поддерживает фильтры исключений, поэтому будет работать следующее:
try { … }
catch (Exception e) when (MyFilter(e))
{
…
}
Тогда метод MyFilter мог бы выглядеть примерно так:
private bool MyFilter(Exception e)
{
return e is ArgumentNullException || e is FormatException;
}
В качестве альтернативы, все это можно сделать встроенным (правая часть оператора when просто должна быть логическим выражением).
try { … }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
…
}
Это отличается от использования оператора if в блоке catch с использованием фильтров исключений не будет для раскрутки стека.
Вы можете скачать Visual Studio 2015, чтобы убедиться в этом.
Если вы хотите продолжить использование Visual Studio 2013, вы можете установить следующий пакет nuget:
Install-Package Microsoft.Net.Compilers
На момент написания это будет включать поддержку C# 6.
Referencing this package will cause the project to be built using the specific version of the C# and Visual Basic compilers contained in the package, as opposed to any system installed version.
Поскольку мне казалось, что эти ответы только коснулись поверхности, я попытался копнуть немного глубже.
Итак, что мы действительно хотели бы сделать, это то, что не компилируется, например:
// 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.
Чтобы он работал на C#, нам нужна временная переменная с правильным базовым типом Exception. Чтобы контролировать поток кода, мы можем добавить несколько веток. Вот оно:
Exception ex;
try
{
throw new ArgumentException(); // for demo purposes; won't be caught.
goto noCatch;
}
catch (ArgumentOutOfRangeException e) {
ex = e;
}
catch (IndexOutOfRangeException e) {
ex = e;
}
Console.WriteLine("Handle the exception 'ex' here :-)");
// throw ex ?
noCatch:
Console.WriteLine("We're done with the exception handling.");
Очевидным недостатком этого является то, что мы не можем перебросить должным образом, и - давайте будем честными - что это довольно уродливое решение. Уродливость можно немного исправить, выполнив удаление ветвей, что немного улучшит решение:
Exception ex = null;
try
{
throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
ex = e;
}
catch (IndexOutOfRangeException e)
{
ex = e;
}
if (ex != null)
{
Console.WriteLine("Handle the exception here :-)");
}
Остается только «переброс». Чтобы это работало, нам нужно иметь возможность выполнять обработку внутри блока «catch» - и единственный способ сделать это - перехватить объект «Exception».
На этом этапе мы можем добавить отдельную функцию, которая обрабатывает различные типы исключений с использованием разрешения перегрузки или для обработки исключения. У обоих есть недостатки. Для начала вот способ сделать это с помощью вспомогательной функции:
private static bool Handle(Exception e)
{
Console.WriteLine("Handle the exception here :-)");
return true; // false will re-throw;
}
public static void Main()
{
try
{
throw new OutOfMemoryException();
}
catch (ArgumentException e)
{
if (!Handle(e)) { throw; }
}
catch (IndexOutOfRangeException e)
{
if (!Handle(e)) { throw; }
}
Console.WriteLine("We're done with the exception handling.");
И другое решение - перехватить объект Exception и обработать его соответствующим образом. Самый дословный перевод этого, исходя из приведенного выше контекста, таков:
try
{
throw new ArgumentException();
}
catch (Exception e)
{
Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
if (ex != null)
{
Console.WriteLine("Handle the exception here :-)");
// throw ?
}
else
{
throw;
}
}
Итак, в заключение:
Ответ Джозефа Дейгла - хорошее решение, но я обнаружил, что следующая структура немного аккуратнее и менее подвержена ошибкам.
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
}
Если вы можете обновить свое приложение до 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. И пример, показывающий разницу.
Throw без исключения сохраняет стек, но throw ex перезапишет его.
В 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");
}
}
В этом примере не показано использование фильтров исключений.
Это стандартный способ фильтрации исключений в C# 6.0.
Посмотрите еще раз, что такое фильтры исключений. В своем примере вы не используете фильтр исключений. В этот ответ есть подходящий пример, опубликованный за год до вашего.
Примером фильтрации исключений может быть catch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }.
в 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");
}
Если вы не хотите использовать оператор 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");
}
Output: General catch.
Если имеется более одного trueException 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");
}
Output: Catch.
И, как вы можете видеть, в MSIL код транслируется не в операторы if, а в Filters, и Exceptions не может быть выброшен из областей, отмеченных Filter 1 и Filter 2, но вместо этого произойдет сбой фильтра, выбрасывающего Exception, также последнее значение для сравнения помещается в стек до того, как команда endfilter определит успешность / неудачу фильтра (Catch 1XORCatch 2 выполнит соответственно):
Значит, вы повторяете много кода в каждом переключателе исключения? Похоже, извлечение метода было бы идеей бога, не так ли?
Итак, ваш код сводится к следующему:
MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }
void Reset(MyClass instance) { /* reset the state of the instance */ }
Интересно, почему никто не заметил этого дублирования кода.
Кроме того, из C# 6 у вас есть фильтры исключений, как уже упоминалось другими. Таким образом, вы можете изменить приведенный выше код на это:
try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{
Reset(instance);
}
Хотел добавить свой короткий ответ в эту и без того длинную ветку. Что не было упомянуто, так это порядок приоритета операторов catch, в частности, вам необходимо знать объем каждого типа исключения, которое вы пытаетесь перехватить.
Например, если вы используете универсальное исключение как Исключение, оно будет предшествовать всем другим операторам catch, и вы, очевидно, получите ошибки компилятора, однако, если вы измените порядок, вы можете связать свои операторы catch (я думаю, что это немного анти-шаблон ), вы можете поместить универсальный тип Исключение внизу, и он будет захватывать любые исключения, которые не обслуживаются выше в вашем блоке try..catch:
try
{
// do some work here
}
catch (WebException ex)
{
// catch a web excpetion
}
catch (ArgumentException ex)
{
// do some stuff
}
catch (Exception ex)
{
// you should really surface your errors but this is for example only
throw new Exception("An error occurred: " + ex.Message);
}
Я настоятельно рекомендую людям просмотреть этот документ MSDN:
Это классическая проблема, с которой рано или поздно сталкивается каждый разработчик C#.
Разрешите разбить ваш вопрос на 2 вопроса. Первое,
Могу ли я поймать сразу несколько исключений?
Короче нет.
Это приводит к следующему вопросу:
Как мне избежать написания повторяющегося кода, учитывая, что я не могу поймать несколько типов исключений в одном блоке catch ()?
Учитывая ваш конкретный образец, в котором значение запаса обходится дешево, мне нравится выполнять следующие шаги:
Итак, код выглядит так:
try
{
WebId = Guid.Empty;
Guid newGuid = new Guid(queryString["web"]);
// More initialization code goes here like
// newGuid.x = y;
WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}
Если генерируется какое-либо исключение, тогда WebId никогда не устанавливается равным наполовину сконструированному значению и остается Guid.Empty.
Если создание резервного значения обходится дорого, а сброс значения намного дешевле, я бы переместил код сброса в его собственную функцию:
try
{
WebId = new Guid(queryString["web"]);
// More initialization code goes here.
}
catch (FormatException) {
Reset(WebId);
}
catch (OverflowException) {
Reset(WebId);
}
Это хорошее «экологическое кодирование», то есть вы заранее думаете о своем коде и объеме данных и следите за тем, чтобы не было утечки наполовину обработанных значений. Приятно следовать этому образцу, спасибо Джеффри!
В C# 7 ответ от Майкла Стума можно улучшить, сохранив читаемость оператора switch:
catch (Exception ex)
{
switch (ex)
{
case FormatException _:
case OverflowException _:
WebId = Guid.Empty;
break;
default:
throw;
}
}
И с C# 8 в качестве выражения переключателя:
catch (Exception ex)
{
WebId = ex switch
{
_ when ex is FormatException || ex is OverflowException => Guid.Empty,
_ => throw ex
};
}
Не потеряете ли вы трассировку стека, если используете throw ex?
Да, в примере выражения переключателя (2-й пример) вы теряете трассировку стека. Спасибо что подметил это. (Чтобы быть ясным: вы не потеряете его в первом примере)
Может быть, попытаться сделать свой код простым, например, поместить общий код в метод, как если бы вы поступили в любой другой части кода, которая не находится внутри предложения catch?
Например.:
try
{
// ...
}
catch (FormatException)
{
DoSomething();
}
catch (OverflowException)
{
DoSomething();
}
// ...
private void DoSomething()
{
// ...
}
Как бы я это сделал, пытаясь найти шаблон просто красиво
Фильтры исключений теперь доступны в C# 6+. Ты можешь сделать
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
В C# 7.0+ вы также можете комбинировать это с сопоставлением с образцом.
try
{
await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
ae.InnerExceptions.Count > tasks.Count/2)
{
//More than half of the tasks failed maybe..?
}
Этот метод предпочтительнее не только потому, что он прост и понятен, но и не требует раскрутки стека, если условия не выполняются, что обеспечивает лучшую производительность и диагностическую информацию по сравнению с повторным вызовом.
Здесь стоит упомянуть. Вы можете ответить на несколько комбинаций (Exception error и exception.message).
Я столкнулся со сценарием использования при попытке привести объект управления в сетку данных с любым содержимым как TextBox, TextBlock или CheckBox. В этом случае возвращенное исключение было таким же, но сообщение изменилось.
try
{
//do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
}
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
}
Хочу предложить кратчайший ответ (еще один функциональный стиль):
Catch<FormatException, OverflowException>(() =>
{
WebId = new Guid(queryString["web"]);
},
exception =>
{
WebId = Guid.Empty;
});
Для этого вам нужно создать несколько перегрузок метода Catch, аналогичных System.Action:
[DebuggerNonUserCode]
public static void Catch<TException1, TException2>(Action tryBlock,
Action<Exception> catchBlock)
{
CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
}
[DebuggerNonUserCode]
public static void Catch<TException1, TException2, TException3>(Action tryBlock,
Action<Exception> catchBlock)
{
CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
}
и так далее сколько угодно. Но вам нужно сделать это один раз, и вы можете использовать его во всех своих проектах (или, если вы создали пакет nuget, мы тоже могли бы его использовать).
И реализация CatchMany:
[DebuggerNonUserCode]
public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
params Type[] exceptionTypes)
{
try
{
tryBlock();
}
catch (Exception exception)
{
if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
else throw;
}
}
p.s. Я не ставил нулевые проверки для простоты кода, подумайте о добавлении проверки параметров.
p.s.2 Если вы хотите вернуть значение из улова, необходимо использовать те же методы Catch, но с return и Func вместо Action в параметрах.
Обновление для C# 9
Используя новые улучшения сопоставления с образцом, созданный в C# 9, вы можете сократить выражение в фильтре исключений. Теперь поймать несколько исключений очень просто:
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception e) when (e is FormatException or OverflowException)
{
WebId = Guid.Empty;
}
Если вы используете .net 4 и выше, я предпочитаю использовать aggregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.a spx