c# - исключения - обработка исключений си




Ловить несколько исключений сразу? (18)

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

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

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

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

(В принципе предоставить еще одну пустую перегрузку Catch которая возвращает себя)

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

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

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

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

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

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


@Micheal

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

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

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


Для полноты, начиная с .NET 4.0, код может быть переписан как:

Guid.TryParse(queryString["web"], out WebId);

TryParse никогда не выдает исключений и возвращает false, если формат неверен, поэтому Guid.Empty указывает 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-parameters реализована на 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, которую невозможно эмулировать в предыдущих версиях. Во-первых, повторный бросок означает больше накладных расходов, чем пропуск улова. Во-вторых, это не семантически эквивалентно. Новая функция сохраняет неиспользуемый стек при отладке вашего кода. Без этой функции аварийный сброс менее полезен или даже бесполезен.

См. roslyn.codeplex.com/discussions/541301 . И пример, показывающий разницу .


Как насчет

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

Как указывали другие, вы можете иметь оператор if внутри вашего блока catch, чтобы определить, что происходит. C # 6 поддерживает Exception Filters, поэтому будет работать следующее:

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

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

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

В качестве альтернативы это может быть все сделано inline (правая часть оператора when просто должна быть логическим выражением).

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

Это отличается от использования оператора if из блока catch , поскольку фильтры исключений не будут разматывать стек.

Вы можете загрузить Visual Studio 2015, чтобы проверить это.

Если вы хотите продолжить использование Visual Studio 2013, вы можете установить следующий пакет nuget:

Install-Package Microsoft.Net.Compilers

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

Ссылка на этот пакет приведет к созданию проекта с использованием конкретной версии компиляторов C # и Visual Basic, содержащихся в пакете, в отличие от любой установленной системы.


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

Кроме того, кажется, что оператор «есть» может немного ухудшить производительность.

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

С C # 7 ответ от Michael Stum может быть улучшен, сохраняя читаемость оператора switch:

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

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

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

Будут выброшены любые другие исключения и код WebId = Guid.Empty; не пострадает. Если вы не хотите, чтобы другие исключения выходили из строя вашей программы, просто добавьте это ПОСЛЕ двух других уловов:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}

в C # 6 рекомендуется использовать Исключительные фильтры, вот пример:

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }

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

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

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

ОРИГИНАЛ:

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

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

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

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

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

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

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

Потому что это, конечно, не является автоматически более читаемым.

Конечно, я оставил три идентичных экземпляра /* write to a log, whatever... */ return; из первого примера.

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

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

Фаза обслуживания для всех, кто может быть относительно новичком в программировании, будет составлять 98,7% или более от общей продолжительности вашего проекта, а бедный щенок, выполняющий техническое обслуживание, почти наверняка будет кем-то другим, кроме вас. И есть очень хороший шанс, что они потратят 50% своего времени на работу, проклинающую ваше имя.

И, конечно же, FxCop лает на вас, и вам также нужно добавить атрибут к вашему коду, который имеет точно zip, чтобы сделать с запущенной программой, и только там, чтобы сообщить FxCop игнорировать проблему, которая в 99,9% случаев полностью правильно помечать. И, извините, я могу ошибаться, но разве этот атрибут «игнорировать» в конечном итоге не скомпилирован в ваше приложение?

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

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

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

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

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

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

Поскольку я чувствовал, что эти ответы только коснулись поверхности, я попытался копать немного глубже.

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

// 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 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, и единственный способ сделать эту работу - захватывающим объектом «Исключение».

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

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

Просто вызовите попытку и поймайте дважды.

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

Просто просто!


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

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

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

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

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

Интересно, почему никто не заметил, что дублирование кода.

С C # 6 вы также имеете exception-filters как уже упоминалось другими. Таким образом, вы можете изменить код выше:

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

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

Например, если вы используете исключение «catch-all» как Exception, оно будет предшествовать всем другим операторам catch, и вы, очевидно, получите ошибки компилятора, однако, если вы отмените порядок, вы можете связать свои заявления catch (бит анти-шаблона, который я думаю ), вы можете поместить весь тип исключения catch-all внизу, и это приведет к захвату любых исключений, которые не удовлетворяли бы выше в вашем блоке 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 (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}




exception-handling