Different errors and finally
Problem
The bill for zero people
The program splits the restaurant bill evenly. You already know about wrong input, and you catch it:
class Program{ static void Main() { try { int bill = int.Parse(Console.ReadLine()); int people = int.Parse(Console.ReadLine()); Console.WriteLine("Per person: €" + bill / people); } catch (FormatException) { Console.WriteLine("Enter whole numbers."); } }}The bill is €90, and someone enters 0 for the number of people. The program stops again:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.Main() in /projects/Bill/Program.cs:line 9catch is there, but it does not help. It catches only FormatException, and this is a different kind of exception.
The idea
Several catch blocks
One try can be followed by several catch blocks, one for each kind of error:
try{ int bill = int.Parse(Console.ReadLine()); int people = int.Parse(Console.ReadLine()); Console.WriteLine("Per person: €" + bill / people);}catch (FormatException){ Console.WriteLine("Enter whole numbers.");}catch (DivideByZeroException){ Console.WriteLine("People cannot be 0.");}| Input | Output |
|---|---|
| 90 and 3 | Per person: €30 |
| 90 and 0 | People cannot be 0. |
| 90 and three | Enter whole numbers. |
The blocks are checked from top to bottom. The first one whose kind matches runs, and only that one. The rest are skipped.
try{ int bill = int.Parse(Console.ReadLine()); int people = int.Parse(Console.ReadLine()); Console.WriteLine("Per person: €" + bill / people);}catch (FormatException){ Console.WriteLine("Enter whole numbers.");}catch (DivideByZeroException){ Console.WriteLine("People cannot be 0.");}Step by step, in words
- First run: the bill is €90, and there are 0 people.
- 90 / 0 is division by zero: DivideByZeroException is thrown.
- The first catch is for FormatException. It does not match, so it is skipped.
- The second one matches. It runs, and only it.
- Second run: "three" is typed in instead of the number of people.
- int.Parse cannot make a number: FormatException is thrown.
- The very first catch matches. The second is never reached.
- The blocks are checked from top to bottom. The first one that matches runs.
More precisely, if you are curious
catchcan also have a condition:catch (FormatException) when (text == ""). The block catches only if the condition is true.
The idea
The family of exceptions
Exceptions are classes, related by inheritance. Here are the most common ones:
Exception├── SystemException│ ├── ArithmeticException│ │ ├── DivideByZeroException│ │ └── OverflowException│ ├── FormatException│ ├── IndexOutOfRangeException│ ├── NullReferenceException│ ├── ArgumentException│ │ ├── ArgumentNullException│ │ └── ArgumentOutOfRangeException│ └── IOException files, network└── ApplicationExceptioncatch catches the kind you name and all of its subclasses. This is the polymorphism from the module on inheritance: a variable of a parent type can point to an object of a subclass. That is why catch (Exception e) catches everything:
static void Split(string billText, string peopleText){ try { int bill = int.Parse(billText); int people = int.Parse(peopleText); Console.WriteLine("Per person: €" + bill / people); } catch (Exception e) { Console.WriteLine(e.GetType().Name + ": " + e.Message); }}
Split("90", "3");Split("90", "0");Split("90", "three");Per person: €30DivideByZeroException: Attempted to divide by zero.FormatException: The input string 'three' was not in a correct format.e.GetType().Name gives the name of the object's real class. The variable is of type Exception, but the object is of a subclass.
More precisely, if you are curious
StackOverflowExceptioncannot be caught at all: .NET stops the program right away.OutOfMemoryExceptioncan be, but it rarely makes sense.ApplicationExceptionwas meant to be the parent of the exceptions programs define. In practice, you make your own exceptions straight fromException.
- Catch as specifically as you can. catch (Exception e) also hides errors you did not expect, such as a bug in your own code.
Try it
Which catch?
What will the program print?
int[] seats = { 4, 2, 6 };try{ Console.WriteLine("Table 4: " + seats[3] + " seats");}catch (ArithmeticException){ Console.WriteLine("Division by zero");}catch (SystemException){ Console.WriteLine("Runtime error");}catch (Exception){ Console.WriteLine("General error");}seats[3] is outside the array of 3 elements. IndexOutOfRangeException is not an ArithmeticException, so the first block is skipped. It is a subclass of SystemException, so the second one matches and runs. The third is never reached, even though it would match too.Try it
Find the bug
Pick the line with the bug.
Exception is the parent of DivideByZeroException. The first catch catches everything, and the second can never be reached. The compiler says so: "CS0160: A previous catch clause already catches all exceptions of this or of a super type ('Exception')". The rule is from the specific to the general: swap the two blocks.The idea
The compiler does not make you
Java has checked exceptions: the compiler does not accept the program until you catch them or declare them. C# has none of those. No exception has to be caught:
static void PrintReceipt(){ Console.WriteLine("Printing the receipt..."); Thread.Sleep(300); Console.WriteLine("Done.");}
PrintReceipt();Printing the receipt...Done.Thread.Sleep pauses the program for 300 milliseconds and can throw an exception, but the compiler asks for nothing. The same goes for int.Parse.
So the responsibility is yours. Which exceptions a method throws is written in its documentation, in the "Exceptions" section. Read it before you decide whether to catch.
The idea
finally: always
The till's printer has to be freed after every receipt, or the next one will not print. Whatever happens, success or error:
static void PrintReceipt(string qtyText){ Console.WriteLine("The printer is busy."); try { int qty = int.Parse(qtyText); Console.WriteLine("Coffee: " + qty + " x €2 = €" + qty * 2); } catch (FormatException) { Console.WriteLine("Wrong quantity: " + qtyText); } finally { Console.WriteLine("The printer is free."); }}
PrintReceipt("3");PrintReceipt("three");The printer is busy.Coffee: 3 x €2 = €6The printer is free.The printer is busy.Wrong quantity: threeThe printer is free.finally runs always: when everything goes well, when the exception is caught, and even when none of these catch blocks catches it. In that last case finally runs, and the exception keeps going up to the next try that can catch it.
Why not just a line after the try/catch? Because that line is skipped if the exception is of a kind that catch does not catch, or if try has a return. finally is not skipped.
More precisely, if you are curious
- You can also have
trywithfinallyand nocatch: the exception is not caught, but the cleanup runs.
- For resources that have to be closed, such as files and database connections, there is a shorter form:
using. You will meet it when you work with files and databases. It closes the resource by itself, just asfinallywould.
finallydoes not run only if the whole program is stopped, for example withEnvironment.Exit(0).
- If the exception is not caught anywhere in the program, .NET may stop it without running the
finallyblocks. So catch exceptions at least inMainif there is something important infinally.
Try it
finally and return
What will the program print?
static string Reserve(int guests){ try { if (guests > 6) { return "No table for " + guests; } return "Table for " + guests; } finally { Console.WriteLine("Logged the attempt."); }}
Console.WriteLine(Reserve(8));A return in try does not skip finally. The method runs finally first, then returns the text. That is why "Logged the attempt." is printed before the result, which WriteLine gets only when the method is done.
Exercise
Programs with several catch blocks
Solve these in your editor. Do not print prompts.
1. Price per person. The prices are in the array { 120, 75, 300 }. Read a product number (from 1) and the number of people, and print the price per person with integer division. Catch three things: a product that does not exist, zero people, and everything else (then print the exception's message).
| Input | Output |
|---|---|
| 2 and 3 | Per person: €25 |
| 5 and 2 | No such product. |
| 1 and 0 | The number of people cannot be 0. |
| 1 and two | Error: The input string 'two' was not in a correct format. |
2. Calculator. Read a whole number, an operator (+, -, * or /) and a second whole number, each on its own line. Print the calculation. On text instead of a number say "Enter whole numbers.", on division by zero "Division by zero.", and on any other operator "Unknown operator: " followed by the operator.
84/484 / 4 = 2184/0Division by zero.More tasks
- The card terminal. The method
Pay(string amountText)prints "The terminal is connected.", then "Paid €X" or a message about wrong input, and infinally"The terminal is off." Call it with "25" and with "twenty". - Average grade without crashes. Read 5 lines with grades. Skip the ones that are not whole numbers (an exception) or are not from 2 to 6 (an
if). Print the average with integer division. If no grade is valid, the division by zero throws: catch it and print "No valid grades." - Predict and check. Write a
trywithcatchandfinally, where each block prints a line. Predict the output on success, on a caught exception and on an uncaught one, then run it.
Test