When something goes wrong
Problem
12a instead of 12
The concert box office asks how many tickets you want. A ticket is €15.
class Program{ static void Main() { Console.Write("How many tickets? "); int tickets = int.Parse(Console.ReadLine()); Console.WriteLine("Total: €" + tickets * 15); }}With the input 12 the program says "Total: €180". But the customer is in a hurry and hits one letter too many:
How many tickets? 12aUnhandled exception. System.FormatException: The input string '12a' was not in a correct format. at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value) at System.Int32.Parse(String s) at Program.Main() in /projects/Tickets/Program.cs:line 6The program stops. Instead of the total you get a long text, and the "Total" line is never reached. What does this text mean, and how do you keep the program from stopping because of one letter?
The idea
Exception
int.Parse has to return a number. It cannot make a number out of "12a", and returning just any number would be a lie. So the method creates an object that describes the problem and throws it. This object is called an exception.
If nobody catches it, the program stops and shows what happened. This text is called a stack trace, and you read it from top to bottom:
- The first line: the kind of exception and the message.
FormatException: The input string '12a' was not in a correct format.: the number has the wrong format, and here is the text. - The lines with "at": where it happened. This is the call stack from the recursion lesson, from the inside out. At the top is the method that threw the exception, and at the bottom is
Main. - Your line: the first "at" line that points to your file. Here:
Program.cs:line 6. The lines above it are in the code of .NET, and you rarely need them.
The messages are in English because the language writes them, not you.
Try it
Find the bug
The program should print the last score from the test. It stops with this:
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array. at Program.LastScore(Int32[] scores) in /projects/Tickets/Program.cs:line 5 at Program.Main() in /projects/Tickets/Program.cs:line 11Read the stack trace. Which line of the program is the bug on?
Pick the line with the bug.
LastScore, line 5. The line under it is Main, line 11: the method was called from there, but the bug is not there. The first line also says what it is: index 5 with length 5. The last index is scores.Length - 1.The idea
try and catch
An exception can be caught. The code that might throw goes in try. What should happen on an error, you write in catch:
Console.Write("How many tickets? ");try{ int tickets = int.Parse(Console.ReadLine()); Console.WriteLine("Total: €" + tickets * 15);}catch (FormatException){ Console.WriteLine("That is not a whole number.");}Console.WriteLine("Thank you!");12aHow many tickets? That is not a whole number.Thank you!- In the parentheses of
catchyou write which kind of exception you catch. - If everything in
trygoes well,catchis skipped. With the input12the output is "Total: €180" and "Thank you!". - After
catchthe program continues on. It no longer stops.
Console.Write("How many tickets? ");try{ int tickets = int.Parse(Console.ReadLine()); Console.WriteLine("Total: €" + tickets * 15);}catch (FormatException){ Console.WriteLine("That is not a whole number.");}Console.WriteLine("Thank you!");Step by step, in words
- The program asks how many tickets. You type "12a".
- In try: int.Parse tries to make a number out of "12a".
- It cannot. The method throws FormatException.
- Execution jumps straight into catch. The "Total" line is skipped.
- catch is for exactly this kind of error: it prints the message for the customer.
- After catch the program continues. It does not stop.
More precisely, if you are curious
- A variable declared in
try(heretickets) exists only until the closing brace oftry. If you need it after the block too, declare it beforetry.
- In C# you can leave the exception without a name:
catch (FormatException). You do that when you do not need it. If you write a name and do not use it, the compiler gives a warning.
Try it
Which lines run?
What will the program print?
Console.WriteLine("Start");try{ Console.WriteLine("Reading"); int seats = int.Parse("ten"); Console.WriteLine("Got " + seats);}catch (FormatException){ Console.WriteLine("Bad input");}Console.WriteLine("End");"Reading" is printed because it comes before the error. Then Parse throws, and execution jumps straight into catch. "Got" is skipped. After catch the program continues with "End".
The idea
What the exception says
The exception is an object, and e in catch (FormatException e) is the variable that points to it. The object has a way to give you the message: e.Message.
try{ int tickets = int.Parse(Console.ReadLine()); Console.WriteLine("Total: €" + tickets * 15);}catch (FormatException e){ Console.WriteLine("Error: " + e.Message);}12aError: The input string '12a' was not in a correct format.The message is useful to you when you are looking for the bug. To the customer you usually show your own text, in their language, such as "That is not a whole number."
The idea
Ask until it is right
The customer made a mistake. You do not have to stop. Ask again:
int tickets;while (true){ Console.Write("How many tickets? "); try { tickets = int.Parse(Console.ReadLine()); break; } catch (FormatException) { Console.WriteLine("Enter a whole number, for example 3."); }}Console.WriteLine("Total: €" + tickets * 15);12atwelve12How many tickets? Enter a whole number, for example 3.How many tickets? Enter a whole number, for example 3.How many tickets? Total: €180while (true) keeps looping until break stops it. And break is reached only if Parse succeeds. On an error, execution jumps into catch, and after it the loop starts over and asks again.
More precisely, if you are curious
- When wrong input is something normal, C# has a handier way:
int.TryParse. It does not throw an exception. It returnstrueorfalseand writes the number into theoutparameter from the lesson on parameters:
int tickets;Console.Write("How many tickets? ");while (!int.TryParse(Console.ReadLine(), out tickets)){ Console.WriteLine("Enter a whole number, for example 3."); Console.Write("How many tickets? ");}Console.WriteLine("Total: €" + tickets * 15);Exceptions are for situations you do not expect often. For checking input, TryParse is shorter and faster.
Try it
Division by zero
Not every exception comes from the input. The average order value is the revenue divided by the number of orders. But on the first day there are no orders. With whole numbers, revenue / orders stops the program:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.What about decimal numbers?
What will the program print?
int revenue = 90;int orders = 0;double average = (double)revenue / orders;Console.WriteLine(average);NaN ("not a number"). The program goes on with a wrong value without saying anything. So before such a division, check the divisor with if.More precisely, if you are curious
- Here the output is
Infinity. On a computer with regional settings, for example Bulgarian or American, C# prints the∞sign.
- Whole numbers have no value for infinity, so all that is left there is the exception. For decimal numbers, the rules come from the IEEE 754 standard for floating point numbers, which every language follows.
Exercise
Programs that do not stop
Solve these in your editor. Do not print prompts, so your output matches the sample.
1. Text to number. Write a method ToNumber(string text) that returns the text as a double. Read two lines. For each, print the number or a message that it is not a number. Catch the exception in Main.
123.45123.abcThe number is: 123.45Error: 123.abc is not a number.On a computer with Bulgarian regional settings, double.Parse wants a comma: 123,45. See the lesson "Input and output".
2. No such product. The prices are in the array { 120, 75, 300, 45, 89 }. Read a product number from 1 to 5 and print its price. If the number is outside the array, catch the exception and say so. With the input 2 the output is Price: €75.
7There is no product number 7.3. Average order. Read the revenue and the number of orders (whole numbers) and print the average with integer division. If there are no orders, catch the exception. With the input 900 and 4 the output is Average: €225.
900No orders yet.More tasks
- Age, until it is right. Ask for an age until the input is a whole number from 0 to 120. For text like "twenty" say "That is not a number.", and for 150 say "The age must be from 0 to 120." One is an exception, the other is an
if. - Sum of the valid ones. Read 5 lines. Add up the ones that are whole numbers and count the skipped ones: with the input
10,abc,5,7.5,3the output isSum: 18, skipped: 2. - Division on demand. Read two whole numbers and print the quotient. On text instead of a number and on division by zero, the program does not stop. It asks again for both numbers.
- No exception. Solve task 1 with
double.TryParseinstead oftry/catch.
Test