c# генерация - Ловить несколько исключений сразу?




13 Answers

Catch System.Exception и включение типов

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

    throw;
}
исключения обработка

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

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

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

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

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




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




Для полноты, начиная с .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. :)




Если вы не хотите использовать оператор 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 .

На самом деле вы можете поместить в основном любое условие внутри этого условия:

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 в стек до того, endfilter команда endfilter определит успех / сбой фильтра ( Catch 1 XOR Catch 2 будет выполняться соответственно):

Кроме того, в частности Guid имеет метод Guid.TryParse .




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

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

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}



@Micheal

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

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

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




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

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Существует несколько преимуществ инвертирования выражения:

  • Оператор возврата не нужен
  • Код не вложен
  • Нет никакого риска забыть слова «бросок» или «возвращение», которые в решении Иосифа отделены от выражения.

Его можно даже уплотнить до одной строки (хотя и не очень красивой)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Изменить: фильтрация исключений в C # 6.0 сделает синтаксис более чистым и будет иметь ряд других преимуществ по сравнению с любым текущим решением. (что особенно важно, если оставить стек невредимым)

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

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



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



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

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

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



Это классическая проблема, с которой сталкивается каждый разработчик C #.

Позвольте мне разложить ваш вопрос на 2 вопроса. Первый,

Могу ли я поймать несколько исключений сразу?

Короче говоря, нет.

Это приводит к следующему вопросу,

Как избежать дублирования кода, учитывая, что я не могу поймать несколько типов исключений в одном блоке catch ()?

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

  1. Инициализируйте WebId до значения возврата.
  2. Постройте новый указатель во временной переменной.
  3. Установите WebId в полностью созданную временную переменную. Сделайте это окончательным утверждением блока try {}.

Таким образом, код выглядит так:

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

Если какое-либо исключение выбрано, то WebId никогда не будет установлен на полуконструированное значение и останется Guid.Empty.

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

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



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

Например, если вы используете исключение «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:

Иерархия исключений




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

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

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




Related

c# .net exception exception-handling