Exception Handling: try, catch, and finally
C#'s exception-based error model, and how it contrasts with returning explicit error values.
What you'll learn
- Write a try/catch/finally block to handle a thrown exception
- Throw a specific exception type with `throw new SomeException(...)`
- Contrast C#'s exception-based error handling with an explicit (result, error) return convention
Explanation
C# handles errors with exceptions, a fundamentally different model from languages that use an explicit (result, error) return convention. Instead of returning an error value the caller must remember to check, a C# method that encounters a problem throws an exception object: throw new DivideByZeroException("division by zero"); -- this immediately stops normal execution and unwinds the call stack, looking for a matching catch block.
A try block wraps code that might throw; one or more catch blocks handle specific exception types that might come out of it: catch (DivideByZeroException ex) { ... } only catches that exception type (or one derived from it) -- an unmatched exception keeps propagating up the call stack, potentially crashing the program if nothing ever catches it. ex.Message gives you the human-readable string passed to the exception's constructor.
A finally block, if present, always runs after the try/catch, whether an exception was thrown and caught, thrown and not caught, or never thrown at all -- it's the right place for cleanup code (closing a file, releasing a resource) that must happen no matter what.
This is a genuine, worthwhile design tradeoff to understand: an explicit (result, error) return makes every failure point visible directly in the code, at the cost of some repetition; C#'s exceptions keep the "happy path" free of error-checking clutter, at the cost of failure points being less visible at a glance -- an exception can, in principle, come from almost any line. Neither approach is strictly better; they're different, deliberate tradeoffs.
Guided lab
Fill in the blank: catching a thrown exception
Fill in the missing keyword that introduces an exception handler, then predict the output.
using System;
int SafeDivide(int a, int b)
{
if (b == 0)
{
throw new DivideByZeroException("division by zero");
}
return a / b;
}
try
{
int result = SafeDivide(10, 0);
Console.WriteLine($"Result: {result}");
}
____ (DivideByZeroException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
Console.WriteLine("Done.");
}Stuck? Get a hint.
Common mistakes
- Writing a `catch` block for a base exception type like the general `Exception` everywhere, hiding bugs that a more specific `catch (SpecificException ex)` would have surfaced clearly instead.
- Assuming code after a `finally` block's cleanup won't run if an exception is still propagating -- `finally` runs, but an uncaught exception still continues propagating afterward.
- Using exceptions for ordinary, expected outcomes (like a user typing invalid input) instead of reserving them for genuinely exceptional situations -- throwing/catching has real performance cost and can make normal control flow harder to follow.
Knowledge check
Takeaway
Catch the most specific exception type you can meaningfully handle, use `finally` for cleanup that must run no matter what, and remember exceptions trade explicit-but-repetitive error visibility for a cleaner happy path with less-visible failure points.
Summary
C# uses try/throw/catch/finally for error handling: catch blocks handle specific exception types, finally always runs for cleanup, and this is a genuine design tradeoff against an explicit (result, error) convention.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.