Catching Exceptions

Throwing a particular exception type enables the catcher to use the exception’s type itself to identify the problem. It is not necessary, in other words, to catch the exception and use a switch statement on the exception message to determine which action to take in light of the exception. Instead, C# allows for multiple catch blocks, each targeting a specific exception type, as Listing 11.2 shows.

Listing 11.2: Catching Different Exception Types
using System;
 
public sealed class Program
{
    public static void Main(string[] args)
    {
        try
        {
            // ...
            throw new InvalidOperationException(
                "Arbitrary exception");
            // ...
        }
        catch(Win32Exception exception) 
            when(exception.NativeErrorCode  == 42)
        {
            // Handle Win32Exception where
            // ErrorCode is 42
        }
        catch(ArgumentException exception)
        {
            // Handle ArgumentException
        }
        catch(InvalidOperationException exception)
        {
            bool exceptionHandled = false;
            // Handle InvalidOperationException
            // ...
            if(!exceptionHandled)
            {
                throw;
            }
        }
        catch(Exception exception)
        {
            // Handle Exception
        }
        finally
        {
            // Handle any cleanup code here as it runs
            // regardless of whether there is an exception
        }
    }
}

Listing 11.2 includes five catch blocks, each handling a different type of exception. When an exception occurs, the execution jumps to the catch block with the exception type that most closely matches the exception. The closeness of a match is determined by the inheritance chain. For example, even though the exception thrown is of type System.Exception, this “is a” relationship occurs through inheritance because System.InvalidOperationException ultimately derives from System.Exception. Since the exception type InvalidOperationException most closely matches the exception thrown, the catch(InvalidOperationException...) block catches the exception and not the catch(Exception...) block.

An additional conditional expression is available for catch blocks. Instead of limiting whether a catch block matches based only on an exception type match, there is also a conditional clause. This when clause allows you to supply a Boolean expression; the catch block handles the exception only if the condition is true. In Listing 11.2, this is an equality comparison operator. For more complex logic, you could make a method call to check for a condition.

Of course, you could also simply place the conditional check as an if block within the catch body. However, doing so causes the catch block to become the handler for the exception before the condition is checked. It is difficult to write code that allows a different catch block to handle the exception in the scenario where the condition is not met. However, with the exception condition,4 it is now possible to examine the program state (including the exception) without having to catch and rethrow the exception.

Use conditional clauses with caution: If the conditional expression itself throws an exception, then that new exception will be ignored and the condition will be treated as false. For this reason, you should avoid throwing exceptions for the exception conditional expression.

Catch blocks must appear in order, from most specific to most general, to avoid a compile-time error. For example, moving the catch(Exception...) block before any of the other exceptions will result in a compile error, since all prior exceptions derive from System.Exception at some point in their inheritance chain.

As shown with the catch (SystemException){ }) block, a named parameter for the catch block is not required. In fact, a final catch without even the type parameter is allowable, as you will see in the next section.

________________________________________

4. Starting with C# 6.0.
{{ snackbarMessage }}