Catch multiple exceptions at once?

2018-12-31 14:52发布

It is discouraged to simply catch System.Exception. Instead, only the "known" exceptions should be caught.

Now, this sometimes leads to unneccessary repetitive code, for example:

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

I wonder: Is there a way to catch both exceptions and only call the WebId = Guid.Empty call once?

The given example is rather simple, as it's only a GUID. But imagine code where you modify an object multiple times, and if one of the manipulations fail in an expected way, you want to "reset" the object. However, if there is an unexpected exception, I still want to throw that higher.

27条回答
临风纵饮
2楼-- · 2018-12-31 15:02
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
查看更多
琉璃瓶的回忆
3楼-- · 2018-12-31 15:09

So you´re repeating lots of code within every exception-switch? Sounds like extracting a method would be god idea, doesn´t it?

So your code comes down to this:

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 */ }

I wonder why no-one noticed that code-duplication.

From C#6 you furthermore have the exception-filters as already mentioned by others. So you can modify the code above to this:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}
查看更多
深知你不懂我心
4楼-- · 2018-12-31 15:11

@Micheal

Slightly revised version of your code:

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

String comparisons are ugly and slow.

查看更多
孤独总比滥情好
5楼-- · 2018-12-31 15:12

As others have pointed out, you can have an if statement inside your catch block to determine what is going on. C#6 supports Exception Filters, so the following will work:

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

The MyFilter method could then look something like this:

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

Alternatively, this can be all done inline (the right hand side of the when statement just has to be a boolean expression).

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

This is different from using an if statement from within the catch block, using exception filters will not unwind the stack.

You can download Visual Studio 2015 to check this out.

If you want to continue using Visual Studio 2013, you can install the following nuget package:

Install-Package Microsoft.Net.Compilers

At time of writing, this will include support for C# 6.

Referencing this package will cause the project to be built using the specific version of the C# and Visual Basic compilers contained in the package, as opposed to any system installed version.

查看更多
临风纵饮
6楼-- · 2018-12-31 15:15

If you don't want to use an if statement within the catch scopes, in C# 6.0 you can use Exception Filters syntax which was already supported by the CLR in previews versions but existed only in VB.NET/MSIL:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

This code will catch the Exception only when it's a InvalidDataException or ArgumentNullException.

Actually, you can put basically any condition inside that when clause:

static int a = 8;

...

catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
    Console.WriteLine("Catch");
}

Note that as opposed to an if statement inside the catch's scope, Exception Filters cannot throw Exceptions, and when they do, or when the condition is not true, the next catch condition will be evaluated instead:

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Output: General catch.

When there is more then one true Exception Filter - the first one will be accepted:

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Output: Catch.

And as you can see in the MSIL the code is not translated to if statements, but to Filters, and Exceptions cannot be throw from within the areas marked with Filter 1 and Filter 2 but the filter throwing the Exception will fail instead, also the last comparison value pushed to the stack before the endfilter command will determine the success/failure of the filter (Catch 1 XOR Catch 2 will execute accordingly):

Exception Filters MSIL

Also, specifically Guid has the Guid.TryParse method.

查看更多
冷夜・残月
7楼-- · 2018-12-31 15:16

Joseph Daigle's Answer is a good solution, but I found the following structure to be a bit tidier and less error prone.

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

    // Handle exception
}

There are a few advantages of inverting the expression:

  • A return statement is not necessary
  • The code isn't nested
  • There's no risk of forgetting the 'throw' or 'return' statements that in Joseph's solution are separated from the expression.

It can even be compacted to a single line (though not very pretty)

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

    // Handle exception
}

Edit: The exception filtering in C# 6.0 will make the syntax a bit cleaner and comes with a number of other benefits over any current solution. (most notably leaving the stack unharmed)

Here is how the same problem would look using C# 6.0 syntax:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
查看更多
登录 后发表回答