C# · Intermediate

Exception Handling

Things go wrong: a user types “abc” for their age, a file is missing, a server is down. Exceptions let your program notice the problem and respond instead of crashing.

Intermediate 6 min Lesson 17 of 26Examples target C# 13 on .NET 9 unless stated otherwise.
Before you start:Classes and Objects
0/26 in C#
On this page · 6 min · Intermediate

In simple words#

try/catch is a safety net under a trapeze artist. If something goes wrong (an exception), the net catches it and the show continues gracefully instead of crashing.

Exception
An error that happens while the program runs.
try / catch
Run risky code and handle the error.
finally
Code that always runs, error or not.

Learning objectives#

  • Catch exceptions with try and catch
  • Catch specific exception types in the right order
  • Run clean-up code with finally
  • Throw your own and custom exceptions

Explanation#

Code that might fail goes in try { }. If an exception is thrown, C# jumps to the first matching catch block.

Catch specific types first (FormatException, ArgumentOutOfRangeException) and general ones (Exception) last. The first matching catch wins.

finally { } always runs — whether the try succeeded, failed or returned — so it is the place to close files or log the end of an operation. using does this automatically for IDisposable objects.

throw new ArgumentException("...") signals a problem. Create a custom exception by inheriting from Exception when callers need to handle a specific business error.

Exceptions are for unexpected situations. For expected failures, such as invalid user input, prefer the Try pattern: int.TryParse(text, out int value).

Syntax#

C# Illustrative
try
{
    // risky code
}
catch (FormatException ex)   // specific first
{
}
catch (Exception ex)         // general last
{
}
finally
{
    // always runs
}

Code example: Bad input and business errors#

C#Program.cs Illustrative
Console.WriteLine(ParseAge("25"));
Console.WriteLine(ParseAge("abc"));
Console.WriteLine(ParseAge("-3"));


try
{
    Withdraw(balance: 100m, amount: 500m);
}
catch (InsufficientFundsException ex)
{
    Console.WriteLine(
quot;Error:
{ex.Message} (short by {ex.Shortfall})");
} finally { Console.WriteLine("Transaction log closed"); } Console.WriteLine(int.TryParse("42", out int n) ?
quot;Parsed
{n}" : "Not a number");
static string ParseAge(string input) { try { int age = int.Parse(input); if (age < 0) throw new ArgumentOutOfRangeException(nameof(input), "Age cannot be negative"); return
quot;Age:
{age}";
} catch (FormatException) { return
quot;'
{input}' is not a number";
} catch (ArgumentOutOfRangeException ex) { return ex.Message; } } static void Withdraw(decimal balance, decimal amount) { if (amount > balance) throw new InsufficientFundsException(amount - balance); } class InsufficientFundsException(decimal shortfall) : Exception("Insufficient funds") { public decimal Shortfall => shortfall; }
Expected output
Age: 25
'abc' is not a number
Age cannot be negative (Parameter 'input')
Error: Insufficient funds (short by 400)
Transaction log closed
Parsed 42

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. int.Parse("abc") throws FormatException, which the first catch handles.
  2. A negative age is valid number syntax, so the code throws ArgumentOutOfRangeException itself; .NET adds “(Parameter 'input')” to the message.
  3. InsufficientFundsException is a custom exception carrying extra data (Shortfall).
  4. finally runs after the catch, so the log is always closed.
  5. int.TryParse avoids exceptions entirely when bad input is expected.

Real-world use case#

An ASP.NET Core API throws NotFoundException from the service layer; a global exception handler turns it into a 404 response, and any unexpected exception into a 500 with the details logged — never shown to the user.

Common mistakes#

Swallowing exceptions

catch (Exception) { } hides bugs forever. At minimum log the exception; usually let it bubble up.

throw ex; instead of throw;

Re-throwing with throw ex; resets the stack trace, so you lose where the error really happened. Use throw; inside a catch.

Using exceptions for normal flow

Throwing on every invalid form field is slow and noisy. Validate first or use TryParse.

Best practices#

  • Catch only exceptions you can actually handle
  • Order catches from specific to general
  • Use using / await using for anything IDisposable
  • Handle unexpected exceptions in one global place in web apps

Remember this#

try = attempt. catch = handle. finally = always clean up.

Specific catches first, Exception last.

Expected problem → TryParse. Unexpected → exception.

Practice exercise#

Your task: Write SafeDivide(int a, int b) that returns a / b, but returns 0 and prints "Cannot divide by zero" when b is 0, by catching DivideByZeroException.

Need a hint?
Integer division by zero throws DivideByZeroException.
Show solution
C# Illustrative
Console.WriteLine(SafeDivide(10, 2));   // 5
Console.WriteLine(SafeDivide(10, 0));   // Cannot divide by zero, then 0


static int SafeDivide(int a, int b)
{
    try
    {
        return a / b;
    }
    catch (DivideByZeroException)
    {
        Console.WriteLine("Cannot divide by zero");
        return 0;
    }
}

Mini quiz#

Exception Handling quizMultiple choice Question 1 of 3

When does a finally block run?

Score so far: 0/0

Want more? Practise all C# MCQs · C# interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.