Skip to content
wannacode

Throwing and your own exceptions

Problem

The ATM says "take your money"

The account keeps its balance. If the amount to withdraw is negative or there is not enough money, the method simply does nothing:

public void Withdraw(int amount)
{
if (amount > 0 && amount <= Balance)
{
Balance -= amount;
}
}

The ATM uses it like this:

Account account = new Account(100);
account.Withdraw(500);
Console.WriteLine("Take your money.");
Console.WriteLine("Balance: €" + account.Balance);
Output
Take your money.
Balance: €100

The customer asks for €500 and has €100. The method quietly refuses, the ATM does not notice and says "Take your money." The error gets lost. The method could return true or false, but the caller can easily forget to check, and false does not say why.

The idea

throw: I refuse and I say why

Instead of keeping quiet, the method throws an exception:

public void Withdraw(int amount)
{
if (amount <= 0)
{
throw new ArgumentException("The amount must be positive");
}
if (amount > Balance)
{
throw new ArgumentException("Not enough money");
}
Balance -= amount;
}

throw ends the method like return. The line Balance -= amount is never reached. But instead of a value, the method sends an exception, and the caller cannot miss it:

Account account = new Account(100);
try
{
account.Withdraw(500);
Console.WriteLine("Take your money.");
}
catch (ArgumentException e)
{
Console.WriteLine("Declined: " + e.Message);
}
Console.WriteLine("Balance: €" + account.Balance);
Output
Declined: Not enough money
Balance: €100
  • new creates the exception object, like any object. The message goes into its constructor.
  • ArgumentException is the ready-made exception for "the argument is wrong". You use it in exactly this kind of check.
More precisely, if you are curious
  • ArgumentOutOfRangeException is for a number outside the allowed limits, and InvalidOperationException is for an object that is not in a state for this action, such as withdrawing from a closed account.
  • throw works only with an object of a subclass of Exception.

See it

The exception falls through

public void Withdraw(int amount)
{
if (amount > Balance)
{
throw new ArgumentException("Not enough money");
}
Balance -= amount;
}
static void PayRent(Account account)
{
account.Withdraw(800);
Console.WriteLine("The rent is paid.");
}
try
{
PayRent(new Account(500));
}
catch (ArgumentException e)
{
Console.WriteLine("Declined: " + e.Message);
}
Step by step, in words
  1. Main calls PayRent for an account with €500.
  2. PayRent calls Withdraw(800): a new frame on top.
  3. 800 > 500: the method throws an exception.
  4. The balance does not change: the frame is thrown away without finishing its lines.
  5. PayRent has no catch: "The rent is paid." is not printed, and the frame is thrown away.
  6. The catch in Main catches the exception, and the program continues from here.

Remember the call stack from the recursion lesson. Main calls PayRent, and it calls Withdraw. The frames sit one on top of another.

When Withdraw throws, the exception looks for a catch by going down the stack. A frame without a matching catch is thrown away right away, without finishing its lines. That is what happens to PayRent too. Only Main has a catch, and the program continues from there.

If Main had no catch either, it would be thrown away too. Then the program stops and prints a stack trace: exactly these frames, from the inside out.

Try it

What reaches the screen?

The account has €50, and the order is €120.

What will the program print?

static void Checkout(Account account)
{
Console.WriteLine("Paying for the order");
account.Withdraw(120);
Console.WriteLine("The order is paid");
}
Account account = new Account(50);
try
{
Checkout(account);
Console.WriteLine("Sending confirmation");
}
catch (ArgumentException e)
{
Console.WriteLine("Declined: " + e.Message);
}
Console.WriteLine("Balance: €" + account.Balance);
Answer

Try it

Find the bug

The rent is €800, and the account has €500. The program says "The rent is paid." Which line is the bug on?

Pick the line with the bug.

More precisely, if you are curious
  • Sometimes a method catches, does something, such as writing to a log, and throws the same exception on: throw; in catch. Only throw;, without a name, keeps the original stack trace. throw e; starts it over from this line.
  • You can also throw a different exception that says better what happened. Then pass the old one as the second argument of the constructor: new InvalidOperationException("The rent is not paid", e). You will see both in the stack trace.

The idea

Your own exception

"Not enough money" is text. If the ATM wants to suggest "Deposit €30 more", it has to know how much is missing. Pulling the number out of the text is fragile. Better make your own exception class with a field:

class InsufficientFundsException : Exception
{
public int Shortfall { get; }
public InsufficientFundsException(int shortfall) : base("Short by €" + shortfall)
{
Shortfall = shortfall;
}
}

Your own exception is an ordinary class that inherits from Exception. The constructor passes the message to the parent with : base(...). The method throws it when there is not enough money:

public void Withdraw(int amount)
{
if (amount <= 0)
{
throw new ArgumentException("The amount must be positive");
}
if (amount > Balance)
{
throw new InsufficientFundsException(amount - Balance);
}
Balance -= amount;
}

And the caller catches the two cases separately:

Account account = new Account(100);
try
{
account.Withdraw(130);
}
catch (InsufficientFundsException e)
{
Console.WriteLine(e.Message);
Console.WriteLine("Deposit €" + e.Shortfall + " more or choose a smaller amount.");
}
catch (ArgumentException e)
{
Console.WriteLine("Declined: " + e.Message);
}
Output
Short by €30
Deposit €30 more or choose a smaller amount.
More precisely, if you are curious
  • Exception names always end in Exception.
  • Your own exception can also inherit from a more specific ready-made class, such as ArgumentException. Then a catch for the parent catches it too. Here it is separate, so the order of the two catch blocks does not matter.

The idea

Your own or ready-made?

In C# your own exception inherits from Exception, and that is it: there are no checked exceptions. The question is a different one: do you need your own at all?

A ready-made exception, when the meaning matches: ArgumentException for a wrong argument, InvalidOperationException for an action at the wrong moment. Your own, when the caller has to tell it apart from the others and react in its own way, or when the exception has to carry data, like shortfall.

Try it

Fill in the blank

An exception for an invalid age. What goes in the blank so that the message reaches the parent?

class InvalidAgeException : Exception
{
    public InvalidAgeException(string message) : (message)
    {
    }
}

Exercise

Exceptions in classes

Solve these in your editor.

1. Music shop. The shop plays instruments in a demo, but not every one can be played.

  • An abstract class MusicalInstrument with a name, a brand and a price (read-only properties), and a constructor.
  • An interface IPlayable with a method Play().
  • Guitar and Piano inherit from the instrument and can play. Drum only inherits.
  • NotPlayableException : Exception.
  • MusicShop with a method DemoInstrument(MusicalInstrument instrument): if the instrument can play (is IPlayable playable), it plays it, otherwise it throws NotPlayableException.
  • In Main, demo a Fender Stratocaster guitar (€1299), Pearl Export drums (€849) and a Yamaha P-45 piano (€499) from an array. Catch the exception for each instrument separately, so the demo goes on.
Output
Fender Stratocaster, €1299.00
The guitar plays a chord.
Pearl Export, €849.00
Pearl Export cannot be played in a demo.
Yamaha P-45, €499.00
The piano plays a scale.

2. An account with rules. Add to Account a method Deposit(int amount) that throws ArgumentException for an amount of 0 or less. Withdrawing throws InsufficientFundsException from the lesson. The account starts with €100. Read the number of operations and then the operations themselves, each on its own line: "deposit 50" or "withdraw 500". After each one, print the balance or why the operation was declined.

Input
4
deposit 50
withdraw 500
withdraw -5
withdraw 120
Output
Balance: €150
Short by €350. Balance: €150
The amount must be positive. Balance: €150
Balance: €30

3. Invalid age. Person has a name and an age. The Age property throws InvalidAgeException from the card before this one if the age is below 0 or above 150. The constructor also goes through Age, so that nobody can create a person with an invalid age. Create Maria (19), Peter (−3) and Ivan (200).

Output
Maria, 19 years old
Peter: Invalid age: -3
Ivan: Invalid age: 200

More tasks

  • Table reservation. A class Restaurant with a number of free tables and a method for reserving one. If the tables have run out, throw your own NoTablesException. If the number of guests is 0 or less, throw the ready-made exception for a wrong argument.
  • A product with stock. A class Product with a quantity in stock and a method Sell(int qty). If there is not enough in stock, throw OutOfStockException, which carries how many items there are. The shop catches the exception and offers to sell the ones in stock.
  • Music shop with a reason. Add to NotPlayableException a field with the instrument that cannot play, and print its price in catch.

Test

Check yourself

1. What does throw new ArgumentException("...") do?
2. main calls a, a calls b, b throws an exception. Only main has a catch. What happens to the lines in a after the call to b?
3. Where is the best place to catch the exception?
4. Which class does your own exception inherit from in C#?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·