Exercise - Catch specific exception types
Earlier in this module, you learned that the exception objects caught by your C# application are instances of an exception class. Generally speaking, your code will catch
one of the following:
- An exception object that's an instance of the
System.Exception
base class. - An exception object that's an instance of an exception type that inherits from the base class. For example, an instance of the
InvalidCastException
class.
Examine exception properties
System.Exception
is the base class that all derived exception types inherit from. Each exception type inherits from the base class through a specific class hierarchy. For example, the class hierarchy for the InvalidCastException
is as follows:
Object
Exception
SystemException
InvalidCastException
Most of the exception classes that inherit from Exception
don't add any additional functionality; they simply inherit from Exception
. Therefore, examining the properties of the Exception
class enables you to understand most exceptions, and how you might use an exception in your code.
Here are the properties of the Exception
class:
- Data: The
Data
property holds arbitrary data in key-value pairs. - HelpLink: The
HelpLink
property can be used to hold a URL (or URN) to a help file that provides extensive information about the cause of an exception. - HResult: The
HResult
property can be used to access to a coded numerical value that's assigned to a specific exception. - InnerException: The
InnerException
property can be used to create and preserve a series of exceptions during exception handling. - Message: The
Message
property provides details about the cause of an exception. - Source: The
Source
property can be used to access the name of the application or the object that causes the error. - StackTrace: The
StackTrace
property contains a stack trace that can be used to determine where an error occurred. - TargetSite: The
TargetSite
property can be used to get the method that throws the current exception.
It's okay if you're feeling a bit overwhelmed by this examination of exception properties, base classes, and inheritance. Don't worry, catching exceptions in your code and accessing an exception's properties are easier than explaining how exceptions and exception properties work.
Note
In this module, you'll focus on using an exception's message property to report the exception in your application's user interface.
Access the properties of an exception object
Now that you understand exception objects and their properties, it's time to start coding.
Update your Program.cs file as follows:
try { Process1(); } catch { Console.WriteLine("An exception has occurred"); } Console.WriteLine("Exit program"); static void Process1() { try { WriteMessage(); } catch { Console.WriteLine("Exception caught in Process1"); } } static void WriteMessage() { double float1 = 3000.0; double float2 = 0.0; int number1 = 3000; int number2 = 0; Console.WriteLine(float1 / float2); Console.WriteLine(number1 / number2); }
Take a minute to review the code.
This is the same code that you saw in the previous unit (the solution code for the challenge activity). You know that an exception is thrown during the
WriteMessage
method. You also know that the exception is caught in theProcess1
method. You'll be using this code to examine exception objects and specific exception types.Update the
Process1
method as follows:static void Process1() { try { WriteMessage(); } catch (Exception ex) { Console.WriteLine($"Exception caught in Process1: {ex.Message}"); } }
Take a minute to examine your updates.
Notice that your updated
catch
clause is catching an instance of theException
class in an object namedex
. Also notice that yourConsole.WriteLine()
method is usingex
to access the object'sMessage
property and display the error message to the console.Although the
catch
clause can be used without arguments, that approach is not recommended. If you don't specify an argument, all exception types are caught and you can't decern between them.In general, you should only catch the exceptions that your code knows how to recover from. Therefore, your
catch
clause should specify an object argument that's derived fromSystem.Exception
. The exception type should be as specific as possible. This helps to avoid catching exceptions that your exception handler isn't able to resolve. You'll update your code to catch a specific exception type later in this exercise.On the File menu, select Save.
Set breakpoint on the following code line:
Console.WriteLine($"Exception caught in Process1: {ex.Message}");
On the Run menu, select Start Debugging
Code execution should pause at the breakpoint.
Hover the mouse cursor over
ex
.Notice that IntelliSense displays the same exception properties that you examined earlier.
Take a minute to examine the information describing the exception object
ex
.Notice that the exception is a
System.DivideByZeroException
exception type and that theMessage
property is set toAttempted to divide by zero.
.Notice that the
StackTrace
property reports the method and line number where the error occurred, along with the sequence of method calls (and line numbers) that led to the error.On the Debug toolbar, select Continue.
Take a minute to examine the console output.
Notice that the exception's
Message
property is included in the output generated by your application:∞ Exception caught in Process1: Attempted to divide by zero. Exit program
Catch a specific exception type
Now that you know the type of exception to catch, you can update your catch
clause to handle that specific exception type.
Update the
Process1
method as follows:static void Process1() { try { WriteMessage(); } catch (DivideByZeroException ex) { Console.WriteLine($"Exception caught in Process1: {ex.Message}"); } }
Save your code, and then start a debug session.
Notice that your updated application reports the same messages to the console.
Although the reported messages are the same, there is an important difference. Your
Process1
method will only catch exceptions of the specific type that it's prepared to handle.To generate a different exception type, update the
WriteMessage
method as follows:static void WriteMessage() { double float1 = 3000.0; double float2 = 0.0; int number1 = 3000; int number2 = 0; byte smallNumber; Console.WriteLine(float1 / float2); // Console.WriteLine(number1 / number2); checked { smallNumber = (byte)number1; } }
Notice the use of the
checked
statement.When performing integral type calculations that assign the value of one integral type to another integral type, the result depends on the overflow-checking context. In a
checked
context, the conversion succeeds if the source value is within the range of the destination type. Otherwise, anOverflowException
is thrown. In an unchecked context, the conversion always succeeds, and proceeds as follows:If the source type is larger than the destination type, then the source value is truncated by discarding its "extra" most significant bits. The result is then treated as a value of the destination type.
If the source type is smaller than the destination type, then the source value is either sign-extended or zero-extended so that it's of the same size as the destination type. Sign-extension is used if the source type is signed; zero-extension is used if the source type is unsigned. The result is then treated as a value of the destination type.
If the source type is the same size as the destination type, then the source value is treated as a value of the destination type.
Note
Integral type calculations that are not inside a
checked
code block are treated as if they are inside anunchecked
code block.Save your code, and then start a debug session.
Notice that a new exception type is caught by the
catch
clause in the top-level statements rather than inside theProcess1
method.Your application prints the following messages to the console:
∞ An exception has occurred Exit program
Note
The
catch
block inProcess1
is not executed. This is the behavior that you wanted. Only catch the exceptions that your code is prepared to handle.
Catch multiple exceptions in a code block
At this point, you may be wondering what happens when multiple exceptions occur in a single code block. Will your code catch
each exception as they occur?
Update the
WriteMessage
method as follows:static void WriteMessage() { double float1 = 3000.0; double float2 = 0.0; int number1 = 3000; int number2 = 0; byte smallNumber; Console.WriteLine(float1 / float2); Console.WriteLine(number1 / number2); checked { smallNumber = (byte)number1; } }
Set breakpoint inside the
WriteMessage()
method on the following code line:Console.WriteLine(float1 / float2);
Save your code, and then start a debug session.
Step through your code one line at a time, and notice what happens after your code handles the first exception.
When the first exception occurs, control is passed to the first
catch
clause that can handle the exception. The code that would generate the second exception is never reached. This means that some of your code is never executed. This could lead to serious issues.Take a minute to consider how you could manage multiple exceptions, and when/why you might not want your code to manage multiple exceptions.
You learned earlier in this module that exceptions should be caught as close to where they occur as possible. With this in mind, you may choose to update the
WriteMessage
method to catch exceptions using its owntry-catch
. For example:static void WriteMessage() { double float1 = 3000.0; double float2 = 0.0; int number1 = 3000; int number2 = 0; byte smallNumber; try { Console.WriteLine(float1 / float2); Console.WriteLine(number1 / number2); } catch (DivideByZeroException ex) { Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}"); } checked { smallNumber = (byte)number1; } }
You could also wrap the code that causes the
OverflowException
in a separatetry-catch
within theWriteMessage()
method.checked { try { smallNumber = (byte)number1; } catch (OverflowException ex) { Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}"); } }
Under what conditions would it be undesirable to catch subsequent exceptions?
Consider the case when your method (or code block) is completing a two part process. Assume that the second part of the process is dependent on the first part completing. If the first part of the process is unable to complete successfully, there's no point in continuing on to the second part of the process. In this case, it's often better to present the user with a message explaining the error condition without attempting the remaining portion or portions of the larger process.
Catch separate exception types in a code block
There are times when variations in your data may cause different types of exceptions.
Clear your breakpoints, and then replace the contents of your Program.cs file with the following code:
// inputValues is used to store numeric values entered by a user string[] inputValues = new string[]{"three", "9999999999", "0", "2" }; foreach (string inputValue in inputValues) { int numValue = 0; try { numValue = int.Parse(inputValue); } catch (FormatException) { Console.WriteLine("Invalid readResult. Please enter a valid number."); } catch (OverflowException) { Console.WriteLine("The number you entered is too large or too small."); } catch(Exception ex) { Console.WriteLine(ex.Message); } }
Take a minute to review this code.
First, the code creates a string array named
inputValues
. The data in the array is intended to represent the input values entered by a user who was instructed to enter numeric values. Depending on the value entered, different exception types may occur.Notice that the code uses the
int.Parse
method to convert the string "input" values to integers. Theint.Parse
code is placed inside atry
code block.Set a breakpoint on the following code line:
int numValue = 0;
Save your code, and then start a debug session.
Step through the code one line at a time, and notice that different exception types are caught.
Recap
Here are a few important things to remember from this unit:
- The
catch
clause should be configured to catch a specific exception type. For example, theDivideByZeroException
exception type. - The properties of an exception object can be accessed within the
catch
block. For example, you can use theMessage
property to inform the application user of an issue. - You can specify two or more
catch
clauses when you need to catch more than one exception type.