Skip to content
Educora
Intermediate17 min8 / 16

Exceptions: catching and throwing errors

Handle errors that happen while a program runs with `try`, `catch` and `finally`, throw your own exceptions with `throw` and learn when not to use them.

Check yourself
In this lesson you will learn
  • Tell a compile error apart from an exception at run time
  • Catch exceptions with try, several catch blocks and finally
  • Throw an exception with throw and create your own exception class
  • Choose approaches such as TryParse for expected mistakes

The compiler finds many mistakes before a program starts, but not all of them. A user types “abc” instead of an age, a file has been deleted, the internet drops, someone divides by zero — these problems only show up while the program is running. In C#, such situations raise an exception. If you do not catch it, the program stops with an “Unhandled exception” message; if you do, you can explain the problem to the user and carry on.

try and catch

Code that might fail goes into a try block. As soon as an exception occurs, the rest of the try block is skipped and control jumps to a matching catch block. Each catch handles a particular exception type, so you can react differently to different errors.

C#
string[] inputs = ["42", "abc", "99999999999"];

foreach (string input in inputs)
{
    try
    {
        int number = int.Parse(input);
        Console.WriteLine($"OK: {number}");
    }
    catch (FormatException)
    {
        Console.WriteLine($"'{input}' is not a number");
    }
    catch (OverflowException)
    {
        Console.WriteLine($"'{input}' is too big for int");
    }
}
Expected output
OK: 42
'abc' is not a number
'99999999999' is too big for int

An exception is also an object: in catch (IndexOutOfRangeException ex), the variable ex holds its details, for example ex.Message — the explanation text. All exceptions derive from the Exception class, so catch (Exception ex) catches any error — always put it last. A finally block runs every time, whether there was an error or not: it is the right place to close a file or free a resource.

C#
int[] scores = [90, 75, 88];

try
{
    Console.WriteLine(scores[5]);
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
finally
{
    Console.WriteLine("This line always runs.");
}

try
{
    int zero = 0;
    Console.WriteLine(10 / zero);
}
catch (Exception ex)
{
    Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
Expected output
Error: Index was outside the bounds of the array.
This line always runs.
DivideByZeroException: Attempted to divide by zero.
ExceptionWhen it happens
FormatExceptionint.Parse("abc") — the text is not a number
DivideByZeroExceptiona whole number is divided by zero
IndexOutOfRangeExceptionan index that does not exist in the array
KeyNotFoundExceptiona key that is not in the dictionary
NullReferenceExceptionusing a member of a variable that is null
FileNotFoundExceptionthe file you try to open does not exist
The most common exceptions

throw: raising an exception yourself

If a method cannot do its job properly, it should throw an exception with throw instead of quietly returning a wrong result. There are many ready-made types: ArgumentException for a bad argument, InvalidOperationException for an operation not allowed in the object's current state. If needed, you can create your own exception class that derives from Exception — a real use of the inheritance from the previous lesson.

C#
var wallet = new Wallet(100);
try
{
    wallet.Pay(30);
    wallet.Pay(250);
    wallet.Pay(10);
}
catch (NotEnoughMoneyException ex)
{
    Console.WriteLine($"Payment failed: {ex.Message}");
}
Console.WriteLine($"Left: {wallet.Balance} AZN");

class NotEnoughMoneyException(string message) : Exception(message);
class Wallet(decimal balance)
{
    public decimal Balance { get; private set; } = balance;
    public void Pay(decimal amount)
    {
        if (amount > Balance)
            throw new NotEnoughMoneyException($"need {amount} AZN, have {Balance} AZN");
        Balance -= amount;
        Console.WriteLine($"Paid {amount} AZN");
    }
}
Expected output
Paid 30 AZN
Payment failed: need 250 AZN, have 70 AZN
Left: 70 AZN
wallet.Pay(10) never ran: after an exception, the rest of the try block is skipped.

When not to use exceptions

Exceptions are for unexpected situations. A user typing something wrong is completely expected, so to check text, int.TryParse is better than try/catch: the code is shorter, runs faster and shows the intention clearly. The same rule applies to dictionaries: TryGetValue checks for a missing key without any error.

C#
string[] answers = ["15", "fifteen", "-3"];

foreach (string answer in answers)
{
    if (!int.TryParse(answer, out int age))
        Console.WriteLine($"'{answer}': please type digits");
    else if (age is < 0 or > 120)
        Console.WriteLine($"'{answer}': age must be 0-120");
    else
        Console.WriteLine($"Age accepted: {age}");
}
Expected output
Age accepted: 15
'fifteen': please type digits
'-3': age must be 0-120

Key points

  • An exception is an error that happens while the program runs; if it is not caught, the program stops.
  • When an exception occurs in a try block, the remaining lines are skipped and the matching catch runs.
  • Specific catch blocks come first and catch (Exception) last; finally always runs.
  • throw new ...Exception("...") reports a bad situation; derive your own exceptions from Exception.
  • For expected mistakes, TryParse and TryGetValue are better than exceptions.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
Which exception does int.Parse("abc") throw?