Intermediate · Exceptions, delegates, events and async · topic 26 of 43 · 30 min
Exceptions: try, catch, finally and custom exceptions
Explanation
What is an exception?
Things go wrong in real programs: the internet drops, a user types letters into an amount box, a file is missing. When .NET meets a problem it cannot handle in the normal flow, it creates an exception object and throws it. Throwing means: stop here and hand the problem upwards to whoever called this code, then their caller, and so on, until some code catches it.
try and catch
Console.Write("Amount: ");
try
{
decimal amount = decimal.Parse(Console.ReadLine() ?? "");
Console.WriteLine($"You entered {amount}");
}
catch (FormatException)
{
Console.WriteLine("That is not a number.");
}Several catch blocks and the Exception object
try
{
string text = File.ReadAllText("prices.txt");
int first = int.Parse(text.Split(',')[0]);
Console.WriteLine(100 / first);
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Missing file: {ex.FileName}");
}
catch (DivideByZeroException)
{
Console.WriteLine("The first price is zero.");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
throw; // let it continue upwards after logging
}Every exception has a Message (a human description), a StackTrace (the chain of method calls that led to it) and sometimes an InnerException (an earlier error that caused this one). Log all three when something unexpected happens.
finally: always runs
Console.WriteLine("Start");
try
{
Console.WriteLine("Working");
throw new InvalidOperationException("Printer jammed");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Caught: {ex.Message}");
}
finally
{
Console.WriteLine("Cleanup");
}
Console.WriteLine("End");For objects that implement IDisposable (files, connections), a using statement is the easy form of try/finally: it calls Dispose for you.
Throwing your own exceptions
static void Withdraw(decimal balance, decimal amount)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
if (amount > balance)
throw new InsufficientFundsException(balance, amount);
}
public class InsufficientFundsException : Exception
{
public decimal Balance { get; }
public decimal Requested { get; }
public InsufficientFundsException(decimal balance, decimal requested)
: base($"Cannot withdraw {requested}; balance is only {balance}")
{
Balance = balance;
Requested = requested;
}
}throw; versus throw ex;
Inside a catch block, throw; rethrows the same exception and keeps its original stack trace, so you can still see the line where it really started. throw ex; resets the stack trace to the catch block, hiding where the error came from. Always use throw; to rethrow. To add context, wrap it: throw new OrderException("Could not save order 1001", ex); which keeps the original as InnerException.
Exception filters
try { await client.GetStringAsync(url); }
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
Console.WriteLine("Page not found");
}Good habits and common mistakes
- Empty catch blocks (catch { }) hide bugs. At least log the error.
- Catching Exception everywhere: catch only what you can handle; let the rest reach a top-level handler that logs it.
- Using exceptions for normal decisions: checking input with TryParse is clearer and much faster than catching FormatException.
- throw ex; loses the stack trace: use throw;.
- Showing ex.StackTrace to end users: it can leak internal details. Show a friendly message and log the details.
| Exception | Usual cause |
|---|---|
| NullReferenceException | Using a member of null |
| ArgumentException / ArgumentNullException / ArgumentOutOfRangeException | A method got a bad argument |
| FormatException | Parsing text that is not in the right format |
| InvalidOperationException | The object is in the wrong state for this call (e.g. First on empty) |
| KeyNotFoundException | Dictionary key missing |
| IndexOutOfRangeException | Array index outside 0..Length-1 |
| FileNotFoundException / IOException | File problems |
| HttpRequestException | A web request failed |
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Exceptions: try, catch, finally and custom exceptions · 1/7
In this lesson you will handle errors without crashing.
Prefer a video by a teacher? Find free videos on “C# exception handling try catch finally custom exception throw” on YouTube ↗
In real life: An ATM withdrawal with clear errors
An ATM must never crash halfway. It checks the amount, uses a custom exception for low balance so the screen can show a precise message, and always logs the attempt in finally, whether it worked or not.
decimal balance = 3000m;
foreach (decimal request in new[] { 2000m, 5000m, -100m })
{
try
{
if (request <= 0) throw new ArgumentException("Amount must be positive");
if (request > balance) throw new InsufficientFundsException(balance, request);
balance -= request;
Console.WriteLine($"Dispensed {request}. Balance {balance}");
}
catch (InsufficientFundsException ex) { Console.WriteLine(ex.Message); }
catch (ArgumentException ex) { Console.WriteLine($"Invalid: {ex.Message}"); }
finally { Console.WriteLine($"Logged request of {request}"); }
}
public class InsufficientFundsException(decimal balance, decimal requested)
: Exception($"Cannot withdraw {requested}; balance is only {balance}") { }Practice (0/4 done)
What does this print? try { Console.Write("A"); throw new Exception(); } catch { Console.Write("B"); } finally { Console.Write("C"); }
Why is throw; better than throw ex; inside a catch?
Which order of catch blocks compiles?
Write a method ParseAge(string? input) that returns an int age. Throw ArgumentNullException if input is null, a custom InvalidAgeException if it is not a number or is outside 0 to 120. In the calling code, catch InvalidAgeException and print its message, and use finally to print "Validation finished".
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Delegates, lambdas, Func, Action and PredicateExceptions, delegates, events and async · same module
- Events: letting objects notify each otherExceptions, delegates, events and async · same module
- async and await: waiting without blockingExceptions, delegates, events and async · same module
- Exceptions: try, catch and finallyJava Programming (Core to Spring Boot) · Exceptions and generics
- Error handling: try, catch, finally and throwJavaScript Programming (language in depth) · Modules, errors, JSON and dates
- Exceptions: throw, try, catch and noexceptC++ Programming (modern C++ and DSA) · Exceptions, concurrency and building projects with CMake
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
When does a finally block run?
Answer: Always, whether or not an exception happened
finally is for guaranteed cleanup.
What happens to an exception that nobody catches?
Answer: The program crashes and shows the error
Unhandled exceptions end the process.
A custom exception class should inherit from...
Answer: Exception
Custom exceptions derive from Exception (or a more specific exception).
What does when do in catch (IOException ex) when (...)?
Answer: Adds a condition that must be true for the catch to run
It is an exception filter.
Which is the better way to handle a user typing "abc" for a number?
Answer: Use int.TryParse and check the result
Expected bad input is normal flow; TryParse avoids exceptions.
What happens to an exception nobody catches in main?Related
Answer: The thread ends and the stack trace is printed
Uncaught exceptions end the thread.
null.length throws which error?Related
Answer: TypeError
Reading a property of null is a TypeError.
Which base class should your own exception types usually derive from?Related
Answer: std::exception (often via std::runtime_error)
Then generic handlers for std::exception catch them too, and what() works.
When does the else block of a try statement run?Related
Answer: Only when the try block raised no exception
else runs only on success. finally is the block that runs every time.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 4 more topics in “Exceptions, delegates, events and async” before its exam: Delegates, lambdas, Func, Action and Predicate, Events: letting objects notify each other, async and await: waiting without blocking, Files and JSON with System.Text.Json.
- Module 7 exam: Exceptions, delegates, events and async · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Web APIs with ASP.NET Core and EF Core. 43 topics left in the course before the final exam.
← Previous: LINQ in depth: grouping, joins, deferred execution, IQueryable