Recipe 5.4 Handling Derived Exceptions Individually
Problem
You have an exception hierarchy
that consists of a base exception class and multiple derived
exception classes. At some point in your code, you want to handle
only one or two of these derived exceptions in a specific manner. All
other derived exceptions should be handled in a more generic manner.
You need a clean way to target specific exceptions in an exception
class hierarchy to be handled differently from the rest.
Solution
The exception handlers for C#
allow for multiple catch clauses to be
implemented. Each of these catch clauses can take
a single parameter—a type derived from the
Exception class. An exception handler that uses
multiple catch clauses is shown here:
try
{
int d = 0;
int z = 1/d;
}
catch(DivideByZeroException dbze)
{
Console.WriteLine("A divide by zero exception occurred. Error message == "
+ dbze.Message);
}
catch(OverflowException ofe)
{
Console.WriteLine("An Overflow occurred. Error message == " + ofe.Message);
}
catch(Exception e)
{
Console.WriteLine("Another type of error occurred. Error message == "
+ e.Message);
}
This code produces the following output:
A divide by zero exception occurred. Error message == Attempted to divide by zero.
Discussion
Notice the exception types that each catch clause
handles in this try-catch block. These specific
exception types will be handled on an individual basis within their
own catch block. Suppose the
try block looked as follows:
try
{
int z2 = 9999999;
checked{z2 *= 999999999;}
}
We would get the following message:
An Overflow occurred. Error message == Arithmetic operation resulted in an overflow.
Now, since the OverflowException is being thrown,
it is handled in a totally different catch block.
You may be thinking that you could do the same thing in a single
catch block using an if-else
statement. An example of this is shown here:
catch(Exception e)
{
if (e is OverflowException)
Console.WriteLine("An Overflow occurred. Error message == " + e.Message);
else if (e is DivideByZeroException)
Console.WriteLine("A divide by zero exception occurred. Error message == " +
e.Message);
else
Console.WriteLine("Another type of error occurred. Error message == " +
e.Message);
}
The if-else statements are used to check the type
of this exception and then execute the appropriate code. This
structure has two flaws. The first is that the compiler does not
check whether the exceptions are listed in the correct order in the
if-else statements. If an exception class is
placed in the if-else conditional structure after
a class in which it inherits from, the derived class will never be
checked. Consider the following modified catch
clause:
try
{
int d = 0;
int z = 1/d;
}
catch(Exception e)
{
if (e is ArithmeticException)
Console.WriteLine("The base class exception was chosen.");
else if (e is OverflowException)
Console.WriteLine("An Overflow occurred. Error message == " + e.Message);
else if (e is DivideByZeroException)
Console.WriteLine("A divide by zero exception occurred. Error message == " +
e.Message);
else
Console.WriteLine("Another type of error occurred. Error message == " +
e.Message);
}
This code produces the following output:
The base class exception was chosen.
Even though the DivideByZeroException was thrown,
the ArithmeticException is always found first, as
the DivideByZeroException and
OverflowException both have the
ArithmeticException class as their base class.
The second flaw is one of appearance. Using multiple
catch clauses is much easier to read due to its
natural and consistent structure. This is the way the language should
be used, and, therefore, this is what many developers are going to
look for. Other developers reading your code may find it more natural
to read the multiple catch classes rather than the
single catch clause with a decision structure
inside of it. Not everyone may agree with us on this part, but we do
consider structure and consistency an integral part of writing good
code.
There is one case where we would consider using the single
catch clause with the decision structure: when
large amounts of code would have to be duplicated in each
catch clause and there is no way to put the
duplicated code in a finally clause after the
try-catch block.
See Also
See the "Error Raising and Handling
Guidelines" topic in the MSDN
documentation.
|