Exercise - Create and throw an exception
Developers often need to create and throw exceptions from within a method, and then catch those exceptions further down the call stack where they can be handled. Exception handling helps you to ensure the stability of your applications.
In this exercise, you'll start with a sample application that includes a potential error condition inside a called method. Your updated method will throw an exception when it detects the issue. The exception will be handled in a catch block of the code that calls the method. The result is an application that provides a better user experience.
Create a new code project
The first step is to create a code project that you can use during this module.
Open a new instance of Visual Studio Code.
On the File menu, select Open Folder.
On the Open Folder dialog, navigate to your Windows Desktop folder.
On the Open Folder dialog, select New folder.
Name the new folder ThrowExceptions101, and then select Select Folder.
On the Terminal menu, select New Terminal.
You will use a .NET CLI command to create a new console app.
At the TERMINAL panel command prompt, enter the following command:
dotnet new consoleClose the TERMINAL panel.
Review a sample application
Use the following steps to load and review a sample application.
Open the Program.cs file.
On the View menu, select Command Palette.
At the command prompt, enter .net: g and then select .NET: Generate Assets for Build and Debug.
Replace the contents of the Program.cs file with the following code:
// Prompt the user for the lower and upper bounds Console.Write("Enter the lower bound: "); int lowerBound = int.Parse(Console.ReadLine()); Console.Write("Enter the upper bound: "); int upperBound = int.Parse(Console.ReadLine()); decimal averageValue = 0; // Calculate the sum of the even numbers between the bounds averageValue = AverageOfEvenNumbers(lowerBound, upperBound); // Display the value returned by AverageOfEvenNumbers in the console Console.WriteLine($"The average of even numbers between {lowerBound} and {upperBound} is {averageValue}."); // Wait for user input Console.ReadLine(); static decimal AverageOfEvenNumbers(int lowerBound, int upperBound) { int sum = 0; int count = 0; decimal average = 0; for (int i = lowerBound; i <= upperBound; i++) { if (i % 2 == 0) { sum += i; count++; } } average = (decimal)sum / count; return average; }Take a minute to review the code.
Notice that the application performs the following tasks:
The top-level statements use
Console.ReadLine()statements to obtain values forlowerBoundandupperBound.The top-level statements pass
lowerBoundandupperBoundas arguments when calling theAverageOfEvenNumbersmethod.The
AverageOfEvenNumbersmethod performs the following tasks:- Declares local variables used in calculations.
- Uses a
forloop to sum the even numbers betweenlowerBoundandupperBound. The sum is stored insum. - Counts how many numbers are included in the sum. The count is stored in
count. - Stores the average of the summed numbers in a variable named
average. The value ofaverageis returned.
The top-level statements print the value returned by
AverageOfEvenNumbersto the console and then pauses execution.
Configure the debug environment
The sample application reads user input from the console. The DEBUG CONSOLE panel doesn't support reading input from the console. You need to update the launch.json file before you can run this application in the debugger.
Use the EXPLORER view to open the launch.json file.
In the launch.json file, update the
consoleattribute as follows:// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console":"integratedTerminal",The default value for the
consoleattribute isinternalConsole, which aligns to the DEBUG CONSOLE panel. Unfortunately, the DEBUG CONSOLE panel doesn't support console input. TheintegratedTerminalsetting aligns to the TERMINAL panel, which supports console input and output.Save your changes to the launch.json file, and then close the file.
On the Visual Studio Code Run menu, select Start Debugging.
Switch to the TERMINAL panel.
At the "lower bound" prompt, enter 3
At the "upper bound" prompt, enter 11
Notice that the application displays the following message and then pauses:
The average of even numbers between 3 and 11 is 7.To exit the application, press Enter.
Throw an exception in the AverageOfEvenNumbers method
The AverageOfEvenNumbers method is expecting an upper bound that's greater than the lower bound. A DivideByZero error occurs if the lower bound is greater than or equal to the upper bound.
You need to update the AverageOfEvenNumbers method to throw an exception when the lower bound is greater than or equal to the upper bound.
Take a minute to consider how you want to address the issue.
One option is to wrap the calculation of
averageinside atrycode block andcatchtheDivideByZeroexception when it occurs. You can rethrow the exception and then handle it in the calling code.Another option is to evaluate the input parameters before starting the calculations. If
lowerBoundis greater than or equal toupperBound, you can throw an exception.Evaluating the parameters and throwing an exception before starting the calculations is the better choice.
Consider which exception type to throw.
There are two exception types that align with the issue:
ArgumentOutOfRangeException- AnArgumentOutOfRangeExceptionexception type should only be thrown when the value of an argument is outside the allowable range of values as defined by the invoked method. AlthoughAverageOfEvenNumbersdoesn't explicitly define an allowable range forlowerBoundorupperBound, the value oflowerBounddoes imply the allowable range forupperBound.InvalidOperationException: AnInvalidOperationExceptionexception type should only be thrown when the operating conditions of a method don't support the successful completion of a particular method call. In this case, the operating conditions are established by the input parameters of the method.
When you have two or more exception types to choose from, select the exception type that fits the issue more closely. In this case, the two exception types are aligned to the issue equally.
When you have two or more exception types that are aligned to the issue equally, select the most narrowly scoped exception type. The
ArgumentOutOfRangeExceptionexception type is scoped to the arguments passed to the method. TheInvalidOperationExceptionexception type is scoped to the operating conditions of the method. In this case, theArgumentOutOfRangeExceptionexception type is more narrowly scoped than theInvalidOperationExceptionexception type.The
AverageOfEvenNumbersmethod should throw anArgumentOutOfRangeExceptionexception.At the top of the
AverageOfEvenNumbersmethod, to detect the upper bound issue, update your code as follows:if (lowerBound >= upperBound) { } int sum = 0;To create and throw an
ArgumentOutOfRangeExceptionexception, update theifcode block as follows:if (lowerBound >= upperBound) { throw new ArgumentOutOfRangeException("upperBound", "ArgumentOutOfRangeException: upper bound must be greater than lower bound."); }This code line initializes a new instance of the
ArgumentOutOfRangeExceptionclass with the name of the input parameter that causes the exception and a specified error message.
Catch the exception in the calling code
Whenever possible, exceptions should be caught at the call stack level where they can be handled. In this sample application, the parameters of the AverageOfEvenNumbers method can be managed in the calling method (the top-level statements).
Scroll up to the top level statements.
To enclose the
AverageOfEvenNumbersmethod call andConsole.WriteLinestatement inside atrycode block, update your code as follows:try { // Calculate the sum of the even numbers between the bounds averageValue = AverageOfEvenNumbers(lowerBound, upperBound); // Display the result to the user Console.WriteLine($"The average of even numbers between {lowerBound} and {upperBound} is {averageValue}."); }To create the associated
catchclause, enter the following code:catch(ArgumentOutOfRangeException ex) { }Take a minute to consider how you might handle the exception.
To handle this exception, your code needs to do the following:
- Explain the issue to the user.
- Obtain a new value for
upperBound. - Call
AverageOfEvenNumbersusing the newupperBound. - Continue to
catchthe exception if the newupperBoundprovided is still less than or equal tolowerBound.
Continuing to
catchthe exception requires a loop. Since you want to call theAverageOfEvenNumbersmethod at least once, adoloop should be used.To enclose the
tryandcatchblocks inside adoloop, update your code as follows:do { try { // Calculate the sum of the even numbers between the bounds averageValue = AverageOfEvenNumbers(lowerBound, upperBound); // Display the result to the user Console.WriteLine($"The average of even numbers between {lowerBound} and {upperBound} is {averageValue}."); } catch (ArgumentOutOfRangeException ex) { } }A
whileexpression is required to define the exit condition of adoloop. It's difficult to specify the condition before the contents of thedocode block are defined. Completing thecatchcode block will help you to define thewhileexpression that's needed.To explain the issue to the user and obtain a new
upperBound, update yourcatchcode block as follows:catch (ArgumentOutOfRangeException ex) { Console.WriteLine("An error has occurred."); Console.WriteLine(ex.Message); Console.WriteLine($"The upper bound must be greater than {lowerBound}"); Console.Write($"Enter a new upper bound: "); upperBound = int.Parse(Console.ReadLine()); }The updated
catchcode block describes the issue and requires the user to enter a new upper bound. However, what if the user doesn't have a valid upper bound value to enter? What if the user needs to exit the loop rather than enter a value?To provide the user with an option to exit the loop rather than enter a new upper bound, update your
catchcode block as follows:catch (ArgumentOutOfRangeException ex) { Console.WriteLine("An error has occurred."); Console.WriteLine(ex.Message); Console.WriteLine($"The upper bound must be greater than {lowerBound}"); Console.Write($"Enter a new upper bound (or enter Exit to quit): "); string? userResponse = Console.ReadLine(); if (userResponse.ToLower().Contains("exit")) { } else { upperBound = int.Parse(userResponse); } }The updated
catchcode block includes two paths, an "exit" path and a "new upper bound" path.Take a minute to consider the
whileexpression required for thedoloop.If the user enters "Exit" at the prompt, the code should exit the loop. If the user enters a new upper bound, the loop should continue. A
whileexpression that evaluates a Boolean could be used. For example:while (exit == false);The proposed
whileexpression will establish the following behavior:- the
doloop will continue to iterate as long as the Booleanexitis equal tofalse. - the
doloop will stop iterating as soon as the Booleanexitis equal totrue.
- the
To instantiate a Boolean variable named
exit, and useexitto set the exit condition of thedoloop, update your code as follows:bool exit = false; do { try { // Calculate the sum of the even numbers between the bounds averageValue = AverageOfEvenNumbers(lowerBound, upperBound); // Display the result to the user Console.WriteLine($"The average of even numbers between {lowerBound} and {upperBound} is {averageValue}."); exit = true; } catch (ArgumentOutOfRangeException ex) { Console.WriteLine("An error has occurred."); Console.WriteLine(ex.Message); Console.WriteLine($"The upper bound must be greater than {lowerBound}"); Console.Write($"Enter a new upper bound (or enter Exit to quit): "); string? userResponse = Console.ReadLine(); if (userResponse.ToLower().Contains("exit")) { exit = true; } else { exit = false; upperBound = int.Parse(userResponse); } } } while (exit == false);Save your updated code.
On the Run menu, select Start Debugging.
Switch to the TERMINAL panel.
At the "lower bound" prompt, enter 3
At the "upper bound" prompt, enter 3
Notice that the following output is displayed in the TERMINAL panel:
Enter the lower bound: 3 Enter the upper bound: 3 An error has occurred. ArgumentOutOfRangeException: upper bound must be greater than lower bound. (Parameter 'upperBound') The upper bound must be greater than 3 Enter a new upper bound (or enter Exit to quit):At the prompt for a new upper bound, enter 11
Notice that the following output is displayed in the TERMINAL panel:
Enter the lower bound: 3 Enter the upper bound: 3 An error has occurred. ArgumentOutOfRangeException: upper bound must be greater than lower bound. (Parameter 'upperBound') The upper bound must be greater than 3 Enter a new upper bound (or enter Exit to quit): 11 The average of even numbers between 3 and 11 is 7.To exit the application, press Enter.
Congratulations! You've successfully thrown, caught, and handled an exception.
Recap
Here are a few important things to remember from this unit:
- Ensure that your debug environment is configured to support your application requirements.
- Method code should throw an exception when an issue or condition is detected.
- Exceptions should be caught at a level in the call stack where they can be resolved.