Exceptions, and catching the type you actually expect
try/catch/finally, catching specific types first, and why swallowing an exception is worse than not catching it.
After this lesson you can
- Write a try/catch/finally and say what each part guarantees
- Catch a specific exception type instead of a bare catch
- Explain what a custom exception class is for
try
{
var result = Divide(a, b);
Console.WriteLine(result);
}
catch (DivideByZeroException e)
{
Console.WriteLine($"cannot divide: {e.Message}");
}
finally
{
Console.WriteLine("always runs");
}
finally runs whether try succeeded, threw an exception that was
caught, or threw one that was not — even after a return inside
try. It is where cleanup belongs when the type itself is not
IDisposable and using does not apply.
A catch clause with no type — catch { ... } — catches everything,
including exceptions the code has no idea how to handle correctly. An
empty catch block is worse than no catch at all: the program
continues as if nothing happened, with whatever caused the exception
now silently unaddressed, and no trace of it anywhere.
Try it
public static class Solution{ public static string Describe() { try { int[] nums = { 1, 2, 3 }; return "value=" + nums[5]; } catch (IndexOutOfRangeException e) { return "index: " + e.GetType().Name; } catch (Exception e) { return "generic: " + e.GetType().Name; } }}Catch the most specific type first
try { Process(data); }
catch (FileNotFoundException e) { /* a subclass of IOException */ }
catch (IOException e) { /* the broader type */ }
C# checks catch clauses top to bottom and runs the first one whose
type the thrown exception matches. Putting IOException first would
catch everything below it too, and the compiler refuses to compile an
unreachable catch clause that follows — the ordering is enforced,
not just good style.
Custom exceptions
public class InsufficientFundsException : Exception
{
public decimal Needed { get; }
public decimal Available { get; }
public InsufficientFundsException(decimal needed, decimal available)
: base($"needed {needed}, had {available}")
{
Needed = needed;
Available = available;
}
}
A custom exception is a real class, usually with almost no logic in
it — its job is to give a catch clause something specific to match,
and to carry whatever data the caller needs to react correctly, rather
than parsing that information back out of a generic message string.
Try it yourself
2 visible tests · 2 hidden testsImplement ParseAll(List<string> values). For each string, try
int.Parse. Return a Dictionary<string, List<string>> with two
keys: "parsed", every value that parsed successfully (as strings, in
their original form), and "failed", every value that raised a
FormatException. Preserve order within each list, and catch that
specific exception type — nothing broader.
ParseAll(["1","abc","42","3.5"])ParseAll(["1","2","3"])
Sign up to check the hidden tests and save your progress. Sign up