Поймать несколько исключений одновременно?
Не рекомендуется просто ловить System.Exception
, Вместо этого должны быть пойманы только "известные" исключения.
Теперь это иногда приводит к ненужному повторяющемуся коду, например:
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
Интересно: есть ли способ отловить оба исключения и вызвать только WebId = Guid.Empty
позвонить один раз?
Данный пример довольно прост, так как это всего лишь GUID
, Но представьте себе код, в котором вы несколько раз модифицируете объект, и если одна из манипуляций не удалась ожидаемым образом, вы хотите "сбросить" object
, Однако, если есть неожиданное исключение, я все еще хочу бросить это выше.
31 ответ
Ловить System.Exception
и включить типы
catch (Exception ex)
{
if (ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
return;
}
throw;
}
РЕДАКТИРОВАТЬ: я согласен с другими, которые говорят, что, начиная с 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
тест на одной строке сделать его более читабельным? Я так не думаю. Я имею в виду, у меня когда-то был другой программист, яростно спорящий давным-давно, что размещение большего количества кода в одной строке заставило бы его "работать быстрее". Но, конечно, он был совершенно безумным. Попытка объяснить ему (с открытым лицом - что было непросто), как интерпретатор или компилятор разбил бы эту длинную строку на отдельные операторы с одной инструкцией на строку - по существу, идентичные результату, если бы он пошел вперед и просто сделал код читабельным вместо того, чтобы пытаться перехитрить компилятор - не оказал на него никакого влияния. Но я отвлекся.
Насколько менее читабельным это становится, если добавить еще три типа исключений через месяц или два? (Ответ: это становится намного менее читабельным).
Один из главных моментов, на самом деле, заключается в том, что основная часть форматирования текстового исходного кода, на который мы все смотрим каждый день, состоит в том, чтобы сделать его действительно, действительно очевидным для других людей, что на самом деле происходит, когда код выполняется. Потому что компилятор превращает исходный код во что-то совершенно другое и не заботится о вашем стиле форматирования кода. Так что все на одной линии - полный отстой.
Просто говорю...
// 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))
{
…
}
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:
Установить-пакет Microsoft.Net.Compilers
На момент написания, это будет включать поддержку C# 6.
Ссылка на этот пакет приведет к тому, что проект будет построен с использованием конкретной версии компиляторов C# и Visual Basic, содержащихся в пакете, в отличие от любой установленной системы версии.
К сожалению, не в C#, так как для этого вам понадобится фильтр исключений, а C# не предоставляет эту возможность MSIL. VB.NET имеет такую возможность, например,
Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException
Что вы можете сделать, это использовать анонимную функцию для инкапсуляции вашего кода ошибки, а затем вызывать его в этих определенных блоках перехвата:
Action onError = () => WebId = Guid.Empty;
try
{
// something
}
catch (FormatException)
{
onError();
}
catch (OverflowException)
{
onError();
}
Фильтры исключений теперь доступны в C# 6+. Ты можешь сделать
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
Для полноты, начиная с .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. И пример, показывающий разницу.
Обновление для C # 9
Используя новые усовершенствования сопоставления с образцом, сделанные в C# 9, вы можете сократить выражение в фильтре исключений. Теперь поймать несколько исключений очень просто:
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception e) when (e is FormatException or OverflowException)
{
WebId = Guid.Empty;
}
С C# 7 ответ от Michael Stum может быть улучшен при сохранении читабельности оператора switch:
catch (Exception ex)
{
switch (ex)
{
case FormatException _:
case OverflowException _:
WebId = Guid.Empty;
break;
default:
throw;
}
}
catch (Exception ex)
{
if (!(
ex is FormatException ||
ex is OverflowException))
{
throw;
}
Console.WriteLine("Hello");
}
Если вы не хотите использовать 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");
}
Выход: поймать.
И как вы можете видеть в MSIL
код не переведен на if
заявления, но для Filters
, а также Exceptions
нельзя бросать из областей, отмеченных Filter 1
а также Filter 2
но фильтр выбрасывает Exception
вместо этого произойдет сбой, а также последнее значение сравнения, помещенное в стек перед endfilter
команда определит успех / провал фильтра (Catch 1
XOR Catch 2
будет выполнять соответственно):
Также конкретно 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
}
В 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
}
Есть несколько преимуществ обращения выражения:
- Заявление о возврате не требуется
- Код не является вложенным
- Нет риска забыть утверждения "throw" или "return", которые в решении Иосифа отделены от выражения.
Он даже может быть сжат до одной строки (хотя и не очень красиво)
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
}
Это вариант ответа Мэтта (я чувствую, что это немного чище)... используйте метод:
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;
}
}
Сравнение строк уродливо и медленно.
Как насчет
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: см. /questions/13157032/pojmat-neskolko-isklyuchenij-odnovremenno/13157053#13157053 для 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
Соответствующее поведение для 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(StackruException)))
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, проблемы с отладчиком, уродство)
Так почему же этот код не скомпилируется - и как мы можем взломать его таким образом, чтобы он был?
Если мы посмотрим на код, то, что мы действительно хотели бы сделать, это переадресовать вызов. Однако, согласно 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;
}
}
Итак, сделаем вывод:
- Если мы не хотим перебрасывать, мы можем рассмотреть возможность перехвата правильных исключений и их временного хранения.
- Если обработчик прост, и мы хотим повторно использовать код, лучшим решением, вероятно, является введение вспомогательной функции.
- Если мы хотим перезапустить, у нас нет другого выбора, кроме как поместить код в обработчик перехвата 'Exception', который сломает FxCop и неперехваченные исключения вашего отладчика.
Обратите внимание, что я нашел один способ сделать это, но это больше похоже на материал для The Daily WTF:
catch (Exception ex)
{
switch (ex.GetType().Name)
{
case "System.FormatException":
case "System.OverflowException":
WebId = Guid.Empty;
break;
default:
throw;
}
}
Это классическая проблема, с которой сталкивается каждый разработчик C#.
Позвольте мне разбить ваш вопрос на 2 вопроса. Первый,
Могу ли я поймать несколько исключений одновременно?
Короче нет.
Что приводит к следующему вопросу,
Как избежать написания дублирующего кода, если я не могу перехватить несколько типов исключений в одном блоке catch()?
Учитывая ваш конкретный пример, где резервное значение дешево построить, я бы хотел выполнить следующие шаги:
- Инициализируйте WebId в качестве запасного значения.
- Создайте новый Guid во временной переменной.
- Установите для WebId полностью созданную временную переменную. Сделайте это последним утверждением блока try{}.
Итак, код выглядит так:
try
{
WebId = Guid.Empty;
Guid newGuid = new Guid(queryString["web"]);
// More initialization code goes here like
// newGuid.x = y;
WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}
Если выдается какое-либо исключение, то WebId никогда не устанавливается на половину созданного значения и остается Guid.Empty.
Если создание запасного значения стоит дорого, а сброс значения намного дешевле, я бы переместил код сброса в его собственную функцию:
try
{
WebId = new Guid(queryString["web"]);
// More initialization code goes here.
}
catch (FormatException) {
Reset(WebId);
}
catch (OverflowException) {
Reset(WebId);
}
То есть вы повторяете много кода в каждом переключателе исключений? Похоже, что извлечение метода было бы идеей бога, не так ли?
Итак, ваш код сводится к этому:
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-all" в качестве Exception, оно будет предшествовать всем остальным операторам 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:
Может быть, попытаться сохранить ваш код простым, например, помещая общий код в метод, как вы делали бы в любой другой части кода, которая не содержится в предложении catch?
Например:
try
{
// ...
}
catch (FormatException)
{
DoSomething();
}
catch (OverflowException)
{
DoSomething();
}
// ...
private void DoSomething()
{
// ...
}
Просто, как бы я это сделал, пытаясь найти простой красивый шаблон
Здесь стоит упомянуть. Вы можете ответить на несколько комбинаций (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;
}
}
ps Я не ставил нулевые проверки для простоты кода, подумайте о добавлении проверки параметров.
ps2 Если вы хотите вернуть значение из улова, необходимо использовать те же методы Catch, но с возвратами и Func вместо Action в параметрах.
Здесь довольно много ответов. Но я не видел, чтобы использовались новые усовершенствования сопоставления с образцом.
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when (ex is FormatException or OverflowException)
{
WebId = Guid.Empty;
}
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception ex)
{
string ExpTyp = ex.GetType().Name;
if (ExpTyp == "FormatException")
{
WebId = Guid.Empty;
}
else if (ExpTyp == "OverflowException")
{
WebId = Guid.Empty;
}
}
Просто позвоните попробуйте и поймать дважды.
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
try
{
WebId = new Guid(queryString["web"]);
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
Это так просто!