C# · Course navigation

C# exceptions, finally, and using

Throw and catch a custom exception, filter by condition, and release resources reliably.

On this page
Before you start

C# methods and classes.

  • Throw and catch a custom exception, filter by condition, and release resources reliably.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

An exception interrupts normal flow and is passed up the call stack until a matching catch block handles it. Define a custom exception by deriving from Exception and add the data the handler needs. A catch can add a when filter, so it handles the exception only if a condition is true. A finally block runs whether or not an exception occurred, which makes it the place for clean-up.

For resources that must be released, such as files and connections, a class implements IDisposable, and the using statement calls Dispose automatically at the end of its block, even when an exception is thrown. The order in the example is: the body runs, Dispose runs as the block is left, then the matching catch, then finally.

Code example

C#
using System;

class InsufficientFundsException : Exception {
  public int Missing { get; }

  public InsufficientFundsException(int missing) : base("short by " + missing) {
    Missing = missing;
  }
}

class Connection : IDisposable {
  public void Dispose() {
    Console.WriteLine("connection closed");
  }
}

class Program {
  static int Withdraw(int balance, int amount) {
    if (amount > balance) {
      throw new InsufficientFundsException(amount - balance);
    }
    return balance - amount;
  }

  static void Main() {
    try {
      using (var connection = new Connection()) {
        Console.WriteLine("working");
        Withdraw(50, 80);
        Console.WriteLine("not reached");
      }
    } catch (InsufficientFundsException e) when (e.Missing > 10) {
      Console.WriteLine("caught: " + e.Message);
    } finally {
      Console.WriteLine("finally");
    }

    if (!int.TryParse("12x", out int value)) {
      Console.WriteLine("TryParse failed without an exception");
    }
  }
}
Output
working
connection closed
caught: short by 30
finally
TryParse failed without an exception

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1using System;
2
3class InsufficientFundsException : Exception {
4 public int Missing { get; }
5
6 public InsufficientFundsException(int missing) : base("short by " + missing) {
7 Missing = missing;
8 }
9}
10
11class Connection : IDisposable {
12 public void Dispose() {
13 Console.WriteLine("connection closed");
14 }
15}
16
17class Program {
18 static int Withdraw(int balance, int amount) {
19 if (amount > balance) {
20 throw new InsufficientFundsException(amount - balance);
21 }
22 return balance - amount;
23 }
24
25 static void Main() {
26 try {
27 using (var connection = new Connection()) {
28 Console.WriteLine("working");
29 Withdraw(50, 80);
30 Console.WriteLine("not reached");
31 }
32 } catch (InsufficientFundsException e) when (e.Missing > 10) {
33 Console.WriteLine("caught: " + e.Message);
34 } finally {
35 Console.WriteLine("finally");
36 }
37
38 if (!int.TryParse("12x", out int value)) {
39 Console.WriteLine("TryParse failed without an exception");
40 }
41 }
42}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
The custom exception carries the missing amount and a readable message.—
—
The body of the using block starts.—
working
80 is more than 50, so the exception is thrown. The next line is skipped.Missing = 30
working
Leaving the using block calls Dispose before the catch runs.Missing = 30
working
connection closed
The filter is true because 30 > 10, so this handler runs.Missing = 30
working
connection closed
The message built by the exception.Missing = 30
working
connection closed
caught: short by 30
finally runs last.Missing = 30
working
connection closed
caught: short by 30
finally
TryParse reports bad input with a boolean, which is better for expected failures.Missing = 30
working
connection closed
caught: short by 30
finally
TryParse failed without an exception

Behind the scenes

How C# processes this code

C# compiles to code for the .NET runtime, which invokes the program entry point.

Cost and common mistakes

Normal flow costs nothing extra; throwing and catching an exception costs far more than a normal return.

  • Do not catch Exception and ignore it. That hides bugs. Catch the specific types you can handle.
  • Use throw; (not throw e;) to rethrow, so that the original stack trace is kept.
  • Exceptions are slow compared with return values. Use TryParse-style methods for ordinary, frequent failures.

Try it yourself

What happens if the filter is e.Missing > 100?

Reveal answer and reasoning

The filter is false, so this catch does not run and the exception propagates to the caller. finally still runs.

Further reading