Ловити кілька винятків одночасно?


2140

Не рекомендується просто ловити System.Exception. Натомість слід виловлювати лише "відомі" винятки.

Зараз це іноді призводить до непотрібного повторюваного коду, наприклад:

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

Цікаво: Чи є спосіб спіймати обидва винятки і лише WebId = Guid.Emptyодин раз зателефонувати?

Наведений приклад досить простий, оскільки це лише a GUID. Але уявіть код, де ви модифікуєте об'єкт кілька разів, і якщо одна з маніпуляцій не вдалася очікуваним чином, ви хочете "скинути" object. Однак, якщо є несподіваний виняток, я все одно хочу кинути це вище.


5
Якщо ви використовуєте .net 4 і вище, я вважаю за краще використовувати agregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends

2
Bepenfriends - Оскільки System.Guid не кидає AggregateException , було б чудово, якби ви (або хтось) міг опублікувати відповідь, в якій було показано, як ви перетворите її в AggregateException тощо.
weir

1
Про використання AggregateException: кидання сукупної
ексцепції

11
"Не рекомендується просто спіймати System.Exception." -і якщо метод може викинути 32 типи винятків, що робити? написати улов для кожного з них окремо?
giorgim

5
Якщо метод викидає 32 різні типи винятків, він погано записаний. Це або не ловити винятки, які роблять власні дзвінки, він робить FAR занадто багато в одному методі, або більшість / всі 32 мають бути єдиним винятком з кодом причини.
Flynn1179

Відповіді:


2100

Ловіть System.Exceptionі перемикайте на типи

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

    throw;
}

69
На жаль, FxCop (тобто - Visual Studio Code Analysis) не подобається, коли ви ловите виняток.
Ендрю Гарнісон

15
Я погоджуюся з тим, що не ловити Виняток, але, у цьому випадку, улов є фільтром. У вас може бути шар вище, який буде обробляти інші типи виключень. Я б сказав, що це правильно, навіть якщо він включає вилов (Виняток x). Він не змінює потік програми, він просто обробляє певні винятки, а потім дає можливість решті додатків мати справу з будь-якими іншими видами винятків.
lkg

28
Остання версія FxCop не кидає винятку, коли використовується вищевказаний код.
Пітер

28
Не впевнений, що було не так з кодом ОП в першу чергу. Відповідь №1 прийнята майже вдвічі більше рядків і набагато менш читається.
Жоао Браганча

22
@ JoãoBragança: Хоча ця відповідь у цьому прикладі використовує більше рядків, спробуйте уявити, чи маєте ви справу, наприклад, з файлом IO, і все, що ви хочете зробити, це зловити ці винятки та зробити деякі повідомлення в журналі, але тільки ті, які ви очікуєте, що надходять від вашої файли IO методи. Тоді вам часто доводиться стикатися з більшою кількістю (близько 5 і більше) різних типів винятків. У такій ситуації такий підхід може заощадити деякі рядки.
Xilconic

594

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функцію і, наприклад, зателефонуйте їй із кожного блоку лову.

Якщо ви запитаєте мене, другий приклад (із ключовими словами ifта isключовими словами) є і значно менш читабельним, і одночасно значно більш схильним до помилок під час фази обслуговування вашого проекту.

Фаза технічного обслуговування для тих, хто може бути відносно новим у програмуванні, становитиме 98,7% або більше від загального терміну експлуатації вашого проекту, і поганий трюк, який виконує технічне обслуговування, майже напевно буде кимось іншим, ніж ви. І є дуже хороший шанс, що вони витратять 50% свого часу на роботу, проклинаючи ваше ім’я.

І звичайно, FxCop гавкає на вас, і тому ви також повинні додати атрибут до свого коду, який точно пов'язаний із запущеною програмою, і є лише там, щоб сказати 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;
}

36
Коли я вперше натрапив на це питання, я був усюди прийнятою відповіддю. Класно, я можу просто зловити всі Exceptions та перевірити тип. Я думав, що це очистило код, але щось змусило мене повернутися до питання, і я фактично прочитав інші відповіді на питання. Я деякий час жував це, але мушу погодитися з вами. Це більш читабельним і ремонтопрігодни використовувати функцію сушки ваш код , ніж зловити все, перевірте тип порівняння зі списком, код обгорткового та метання. Дякуємо, що пізно завітали та надали альтернативний та здоровий варіант (IMO). +1.
помилково

8
Використання функції обробки помилок не буде працювати, якщо ви хочете включити throw;. Вам доведеться повторити цей рядок коду в кожному блоці лову (очевидно, це не кінець світу, але варто згадати, оскільки це код, який потрібно повторити).
kad81

5
@ kad81, це правда, але ви все одно отримаєте перевагу написання коду реєстрації та очищення в одному місці та, якщо потрібно, змінити його в одному місці, без чіткої семантики лову базового типу винятку, а потім розгалуження на основі тип винятку. І що одне додаткове throw();твердження у кожному блоці вилову - це невелика ціна, яку потрібно заплатити, IMO, і все ще залишає вас у змозі зробити додаткове очищення, що стосується винятку, якщо це необхідно.
Крейг

2
Привіт @Reitffunk, просто використовуй Func<Exception, MyEnumType>замість Action<Exception>. Це з тим Func<T, Result>, Resultщо є типом повернення.
Крейг

3
Я тут повністю згоден. Я теж читав першу відповідь і думав, що здається логічним. Переведено на загальний 1 для всіх обробників виключень. Щось усередині мене змусило всередині себе… так я повернув код. Тоді натрапив на цю красу! Це має бути прийнятою відповіддю
Конор Галлахер

372

Як зазначали інші, ви можете мати ifзаяву всередині блоку вилову, щоб визначити, що відбувається. C # 6 підтримує фільтри винятку, тому буде працювати наступне:

try {  }
catch (Exception e) when (MyFilter(e))
{
    
}

Потім MyFilterметод може виглядати приблизно так:

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

Крім того, все це можна зробити вбудованим (правий бік виразу, коли оператор повинен бути булевим виразом).

try {  }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    
}

Це відрізняється від використання ifоператора з catchблоку, використання фільтрів виключень не розкручує стек.

Ви можете завантажити Visual Studio 2015, щоб перевірити це.

Якщо ви хочете продовжувати використовувати Visual Studio 2013, ви можете встановити наступний пакунок:

Встановити-пакет Microsoft.Net.Compilers

На момент написання цього документа буде включена підтримка C # 6.

Посилання на цей пакет призведе до того, що проект буде побудований за допомогою конкретної версії компіляторів C # і Visual Basic, що містяться в пакеті, на відміну від будь-якої встановленої версії системи.


3
Терпляче чекаючи офіційного виходу 6 ... Я хотів би, щоб це отримало перевірку, коли це станеться.
RubberDuck

@RubberDuck Я вмираю від оператора з нульовим розповсюдженням від C # 6. Намагаюся переконати решту моєї команди, що ризик нестабільної мови / компілятора вартий того. Багато незначних удосконалень з величезним впливом. Що стосується того, щоб бути позначеним як відповідь, не важливо, доки люди усвідомлюють цю волю / це можливо, я задоволений.
Джо

Так ?! Я найближчим часом добре погляну на свою кодову базу. =) Я знаю, що перевірка не важлива, але, враховуючи, що прийнята відповідь незабаром застаріла, я сподіваюся, що ОП повернеться, щоб перевірити це, щоб надати йому належну видимість.
RubberDuck

Частково тому я ще не нагородив його @Joe. Я хочу, щоб це було видно. Ви можете додати приклад вбудованого фільтра для наочності.
RubberDuck

188

На жаль, не в 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();
}

26
Цікава ідея та ще один приклад того, що VB.net має деякі цікаві переваги перед C # інколи
Michael Stum

47
@MichaelStum з цього виду синтаксису я б важко назвати цікавим взагалі ... здриганням
MarioDS

17
Фільтри винятків надходять у №6! Зверніть увагу на різницю використання фільтрів на користь скидання roslyn.codeplex.com/discussions/541301
Arne Deruwe

@ArneDeruwe Дякую за це посилання! Щойно я дізнався ще одну важливу причину не перекидати: throw e;знищує стек-трек і callstack , throw;знищує "тільки" callstack (робить краш-дампи марними!) Дуже вагомий привід не використовувати ні, якщо цього не уникнути!
AnorZaken

1
Станом на C # 6 доступні фільтри! Нарешті.
Денні

134

Для повноти, оскільки .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. реалізована деконструкція зовнішніх параметрів :)


19
Точно - стисло, і ви повністю обходите покарання за виконання винятків, погана форма навмисного використання винятків для керування потоком програми, і м'який фокус розширення вашої логіки перетворення, трохи тут і трохи там .
Крейг

9
Я знаю, що ти мав на увазі, але звичайно Guid.TryParseніколи не повертається Guid.Empty. Якщо рядок має неправильний формат, він встановлює resultвихідний параметр Guid.Empty, але він повертається false . Я згадую про це, тому що я бачив код, який робить речі в стилі Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, що, як правило, неправильно, якщо sможе бути рядкове представлення Guid.Empty.

14
Нічого, ви відповіли на це питання, за винятком того, що це не в дусі питання. Більша проблема полягає в чомусь іншому :(
nawfal

6
Звичайно, правильна схема використання TryParse схожа на те if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }, що не залишає двозначності, як розбитий приклад, коли вхідним значенням насправді може бути рядкове подання Guid.
Крейг

2
Ця відповідь справді може бути правильною щодо Guid.Parse, але вона пропустила всю точку початкового запитання. Що не мало нічого спільного з Guid.Parse, але стосувалося вилучення Exception vs FormatException / OverflowException / тощо.
Conor Gallagher

114

Фільтри винятків тепер доступні в 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..? 
}

Цей спосіб є кращим не тільки тому, що він простий і зрозумілий, але також не повинен розмотати стек, якщо умови не дотримані, що забезпечує кращу інформацію про продуктивність та діагностику порівняно з повторним кроком.
Джо

74

Якщо ви можете оновити свою програму до 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 . І приклад, що показує різницю .


4
Кидок без винятку зберігає стек, але "кинути екс" замінить його.
Іван

32

Якщо ви не хочете використовувати ifзаяву в межах catchобластей, в C# 6.0ви можете використовувати Exception Filtersсинтаксис , який вже був підтриманий СЕК в превью версії , але існувала тільки в 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's, 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 виконає відповідно):

Винятки Фільтри MSIL

Також спеціально Guidє Guid.TryParseметод.


+1 для показу кількох під час фільтрів та надання пояснення того, що відбувається при використанні декількох фільтрів.
steven87vt

26

Завдяки C # 7 відповідь від Майкла Стума можна покращити, зберігаючи читабельність оператора перемикання:

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

3
Це має бути прийнятою відповіддю станом на 2018 рік ІМХО.
MemphiZ

6
Відповідь Мата Дж whenнабагато більш елегантна / відповідна, ніж перемикач.
rgoliveira

@rgoliveira: Я погоджуюсь, що на випадок, поставлений у запитанні, відповідь Мат Дж є більш елегантною та доречною. Однак важко читати, якщо у вас є інший код, який ви хочете виконати, залежно від типу винятку або якщо ви хочете фактично використовувати екземпляр винятку. Всі ці сценарії можуть бути оброблені однаково за допомогою цього твердження перемикача.
Фабіан

1
@Fabian "якщо у вас є інший код, який ви хочете виконати залежно від типу винятку або якщо ви хочете фактично використовувати екземпляр винятку", тоді ви просто зробите інший catchблок, або вам потрібно буде все-таки ввести його. На мій досвід, throw;у вашому catchблоці, ймовірно, кодовий запах.
rgoliveira

@rgoliveira: Використовувати кидок у блок ловлі нормально, у кількох випадках див. посилання . Оскільки в операторі регістру фактично використовується посилання відповідності шаблону, вам не потрібно наводити, якщо ви замінюєте посилання оператора відкидання (підкреслення) іменем змінної. Не зрозумійте мене неправильно, я погоджуюсь з вами, що фільтри винятків - це більш чистий спосіб зробити це, але кілька блоків вилову додають багато фігурних дужок.
Фабіан

20

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

Крім того, здається, що оператор "є" може дещо погіршити продуктивність.

CA1800: Не варто зайвим чином говорити про "розгляд тестування результату" на "оператора замість", але якщо ви це зробите, ви будете писати більше коду, ніж якщо ви виберете кожен виняток окремо.

Так чи інакше, що я б робив:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

19
Але майте на увазі, що ви не можете повторно скинути виняток без втрати сліду стека, якщо зробите це так. (Див. Коментар Майкла Штума до прийнятої відповіді)
Рене

2
Цю модель можна вдосконалити, зберігаючи виняток (вибачте про те, що форматування невдале - я не можу зрозуміти, як ввести код у коментарях): Виняток ex = null; спробуйте {// something} catch (FormatException e) {ex = e; } спіймання (OverflowException e) {ex = e; } if (ex! = null) {// щось інше і розібратися з ex}
Джессі Вайгерт

3
@JesseWeigert: 1. Ви можете використовувати зворотні посилання, щоб надати фрагменту тексту монорозмірний шрифт і світло-сірий фон. 2. Ви все одно не зможете повторно скинути оригінальний виняток, включаючи стек-трек .
Олівер

2
@CleverNeologism, хоча може бути правдою, що використання isоператора може мати незначний негативний вплив на продуктивність, але також правда, що обробник винятків не є надто занепокоєним оптимізацією продуктивності. Якщо ваш додаток витрачає стільки часу на обробку винятків, що оптимізація продуктивності там по-справжньому зміниться в продуктивності додатків, то є й інші проблеми з кодом, які дуже важко розглянути. Сказавши це, мені все ще не подобається це рішення, оскільки ви втрачаєте слід стека і тому, що очищення контекстно видаляється з заяви про вилов.
Крейг

3
Єдиний раз, коли isоператор погіршує продуктивність, це якщо ви пізніше виконаєте asоперацію (отже, вони кваліфікують правило без необхідності ). Якщо все, що ви робите, - це тестування амплуа, без насправді виконувати амплуа, тоді isоператор - саме те, що ви хочете використовувати.
салюс

19

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

18

Це варіант відповіді Метта (я вважаю, що це трохи чистіше) ... використовуйте метод:

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
}

-1 Це буде виконано WebId = Guid.Emtpyу випадку, коли не було викинуто жодного винятку.
Сепстер

4
@sepster Я думаю, що повернення після "// щось" тут мається на увазі. Мені не дуже подобається рішення, але це конструктивний варіант в дискусії. +1 , щоб скасувати downvote :-)
Toong

@Septer toong має рацію, я вважав, що якщо ти хочеш повернутися туди, то ти би поставив його ... Я намагався зробити свою відповідь достатньо загальною, щоб застосувати до всіх ситуацій, якщо інші, хто має подібні, але не точні питання, виграють, оскільки добре. Однак, для гарної міри, я додав відповідь returnдо своєї відповіді. Дякуємо за вклад.
bsara

18

Відповідь Джозефа Дайґла є хорошим рішенням, але я виявив, що наступна структура трохи охайніша і менш схильна до помилок.

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

Ось як виглядатиме та сама проблема, використовуючи синтаксис C # 6.0:

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

2
+1, це найкраща відповідь. Це краще, ніж найкраща відповідь, головним чином, тому що її немає return, хоча перевернення умови також трохи краще.
DCShannon

Я навіть про це не думав. Хороший улов, я додам його до списку.
Стефан Т

16

@Micheal

Трохи переглянута версія вашого коду:

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

Порівняння рядків - потворне і повільне.


21
Чому б просто не використати ключове слово "є"?
Кріс Пітшманн

29
@Michael - Якщо Microsoft представила, скажімо, StringTooLongException, похідну від FormatException, то це все ще виняток формату, лише конкретний. Це залежить від того, чи хочете ви, щоб ви хотіли, щоб семантика "ловити цей точний тип виключення" або "винятки вилучення, які означають, що формат рядка був неправильним".
Грег Бук,

6
@Michael - Також зауважте, що "catch (FormatException ex) має останню семантику, він буде вловлювати все, що випливає з FormatException."
Грег Бук,

14
@ Алекс № "кидок" без "колишнього" несе оригінальний виняток, включаючи оригінальний слід стека, вгору. Додавання "ex" призводить до скидання сліду стека, тому ви дійсно отримуєте інший виняток, ніж оригінал. Я впевнений, що хтось ще може пояснити це краще, ніж я. :)
Саманта Бранхем,

13
-1: Цей код є надзвичайно тендітним - бібліотека розробник може очікувати , щоб замінити throw new FormatException();з , throw new NewlyDerivedFromFormatException();не порушуючи код , використовуючи бібліотеку, і це буде справедливо для всіх обробки винятків , крім випадків , коли хто - то використовував ==замість is(або просто catch (FormatException)).
Сем Харвелл

13

Як щодо

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

Це працює лише в тому випадку, якщо Catch-Code можна повністю перемістити в блок "Try-Block". Але код візуалізації, коли ви робите кілька маніпуляцій з об’єктом, а одна в середині виходить з ладу, і ви хочете "скинути" об'єкт.
Майкл Стум

4
У цьому випадку я б додав функцію скидання і викликав би її з декількох блоків лову.
Моріс


11

Попередження та попередження: Ще один добрий, функціональний стиль.

Що знаходиться у посиланні, не відповідає безпосередньо на ваше запитання, але тривіально розширити його так, щоб виглядати так:

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

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

(В основному надайте ще одне порожнє Catchперевантаження, яке повертається)

Чим більше питання , це чому . Я не думаю, що тут вартість переважає надбання :)


1
Однією з можливих переваг такого підходу є те, що між ловом і повторним викиданням винятку існує семантична різниця, а не її виловлення; в деяких випадках код повинен діяти за винятком, не вловлюючи його. Така річ можлива на vb.net, але не на C #, якщо ви не використовуєте обгортку, написану на vb.net та викликану з C #.
supercat

1
Як діяти за винятком, не сприймаючи його? Я вас цілком не розумію.
nawfal

@nawful ... за допомогою vb filter - filt функції (колишній виняток): LogEx (ex): повернути false ... тоді в рядку лову: catch ex when filt (ex)
FastAl

1
@FastAl Хіба це не те, що дозволяють фільтри виключень у C # 6?
HimBromBeere

@HimBromBeere так, вони є прямими аналогами
FastAl

9

Оновлення 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*/
            }
        }
    }
}

1
Удосконалення мови не є "більш елегантним". У багатьох місцях це фактично створило пекельне обслуговування. Через роки багато програмістів не пишаються тим, якого монстра вони створили. Це не те, що ти звик читати. Це може спричинити "так?" ефект або навіть суворі "WTF". Іноді це заплутано. Єдине, що робить це - зробити код набагато важче зрозуміти для тих, кому потрібно з ним пізніше розібратися в технічному обслуговуванні - лише тому, що один програміст намагався бути "розумним". З роками я дізнався, що ці "розумні" рішення рідко є також і хорошими.
Kaii

1
або в декількох словах: дотримуйтесь можливостей, що надаються мовою. не намагайтеся переосмислити семантику мови лише тому, що вони вам не подобаються. Ваші колеги (а можливо, і майбутні) я вам чесно дякую.
Kaii

Також зауважте, ваше рішення лише наближає семантику C # 6 when, як і будь-яка версія catch (Exception ex) {if (...) {/*handle*/} throw;}. Справжня цінність whenполягає в тому, що фільтр працює до того, як виняток буде вилучений , таким чином, уникнути пошкодження витрат / стеку повторного кидка. Він використовує функцію CLR, яка раніше була доступна лише для VB та MSIL.
Марк Л.

Більш елегантний? Цей приклад настільки великий для такої простої проблеми, і код настільки жахливий, що його навіть не варто було дивитись. Будь ласка, не робіть цього коду чужою проблемою на фактичному проекті.
KthProg

весь ваш IsAnyOfметод можна переписати так простоp_comparisons.Contains(p_parameter)
maksymiuk

7

Оскільки я відчував, що ці відповіді просто торкнулися поверхні, я спробував копати трохи глибше.

Тож, що ми дійсно хотіли б зробити, це те, що не складається, скажімо:

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

Причина, по якій ця команда не компілюється, є цілком очевидною: який тип і значення матиме об’єкт '$ изключення' (які зберігаються тут у змінних '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.");

А інше рішення - зловити об’єкт 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;
    }
}

Отже, на закінчення:

  • Якщо ми не хочемо повторно кидати, ми можемо розглянути можливість вибору правильних винятків та збереження їх у тимчасовому режимі.
  • Якщо обробник простий, і ми хочемо повторно використовувати код, найкращим рішенням є, мабуть, введення хелперної функції.
  • Якщо ми хочемо повторно кинути, у нас не залишається іншого вибору, окрім як поставити код у обробник лову "Виняток", який порушить винятки FxCop та невловимі ваші відладчики.

7

Це класична проблема, з якою стикається кожен розробник C #.

Дозвольте мені розбити ваше питання на 2 запитання. Перший,

Чи можу я впіймати кілька винятків одночасно?

Словом, ні.

Що призводить до наступного питання,

Як уникнути написання дублюючого коду, враховуючи те, що я не можу зловити кілька типів виключень у одному блоці catch ()?

З огляду на ваш конкретний зразок, де резервне значення недорого побудувати, я люблю виконувати наступні кроки:

  1. Ініціалізуйте WebId до запасного значення.
  2. Побудуйте новий Посібник у тимчасовій змінній.
  3. Встановіть WebId на повністю побудовану тимчасову змінну. Зробіть це остаточним твердженням блоку спробу {}.

Так код виглядає так:

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

Це приємне, "екологічне кодування", тобто ви заздалегідь продумуєте свій код та дані про дані та не забуваєте про витік половини оброблених значень. Приємно піти за цією схемою завдяки Джефрі!
Тахір Халід

6

Отже, ви повторюєте багато коду в кожному вимикачі виключення? Здається, вилучення методу було б божою ідеєю, чи не так?

Отже ваш код зводиться до цього:

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

3
"Цікаво, чому ніхто не помітив цього дублювання коду." - е, що? Вся суть питання полягає в усуненні дублювання коду.
Марк Амері

4

Хотів додати свою коротку відповідь до цієї вже довгої нитки. Щось раніше не згадувалося - це порядок пріоритетності висловлювань про вилов, точніше, ви повинні знати про сферу застосування кожного виду винятків, які ви намагаєтеся зловити.

Наприклад, якщо ви використовуєте виняток "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:

Ієрархія винятків


4

Можливо, спробуйте зберегти свій код простим, таким як введення загального коду методом, як ви це робите в будь-якій іншій частині коду, яка не знаходиться в застереженні про вилов?

Наприклад:

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

// ...

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

Тільки, як я це зробив би, намагаючись знайти простий красивий візерунок


3

Зауважте, що я знайшов один із способів зробити це, але це схоже на матеріал для The Daily WTF :

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

9
-1 голос, +5 WTF :-) Це не повинно було бути позначено як відповідь, але він є великим.
Аарон

1
Не має значення, як просто ми могли це зробити. Але він не сидів простою і придумав свою думку, щоб вирішити це. Дуже ціную.
Максимус

2
Хоча насправді цього не робіть, використовуйте Фільтри винятку в C # 6 або будь-який інший відповідь - я ставлю це тут конкретно як "Це один спосіб, але це погано, і я хочу зробити щось краще".
Майкл Стум

ЧОМУ це погано? Мені було спантеличено, що ви не можете використовувати виняток в операторі переключення безпосередньо.
МКеспер

3
@MKesper Я бачу кілька причин - це погано. Це вимагає написання повнокваліфікованих імен класу у вигляді рядкових літералів, що вразливо для помилок, від яких компілятор не може врятувати вас. (Це важливо, оскільки у багатьох магазинах випадки помилок є менш перевіреними, тому тривіальні помилки в них, швидше за все, будуть пропущені.) Він також не зможе співставити виняток, який є підкласом одного із зазначених випадків. І, оскільки це рядки, випадки будуть пропущені інструментами типу "Знайти всі посилання" VS - доречно, якщо ви хочете додати крок очищення скрізь, де потрапляє певний виняток.
Марк Амері

2

Тут варто згадати. Ви можете реагувати на кілька комбінацій (помилка винятку та виняток. Повідомлення).

Я зіткнувся зі сценарієм використання, коли намагався передавати об'єкт управління в мережу даних із вмістом як 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
} 

0

Я хочу запропонувати найкоротшу відповідь (ще один функціональний стиль ):

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

і так далі скільки завгодно. Але вам потрібно зробити це один раз, і ви можете використовувати його у всіх своїх проектах (або, якщо ви створили нукет-пакет, ми могли б і його використовувати).

І реалізація 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 замість дії в параметрах.


-15

Просто зателефонуйте спробувати і зловити двічі.

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

Це просто так просто !!


3
гм це перемагає мету питання. Він задає це питання, щоб позбутися дублікату коду. ця відповідь додає ще дублікат коду.
Джеймс Еш

-23

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

13
Цей приклад не показує використання фільтрів виключень.
користувач247702

Це стандартний спосіб відфільтрувати виняток у c # 6.0
Кашиф

5
Погляньте ще раз, що саме є фільтрами виключень. Ви не використовуєте фільтр винятків у своєму прикладі. У цій відповіді є належний приклад, розміщений за рік до вашого.
користувач247702

6
Прикладом фільтрації винятків може бутиcatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
привітатись
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.