Exceptions
By the end of this lesson you'll be able to stop your C# programs crashing on bad input — catching errors with try/catch/finally , handling specific problems precisely, throwing your own exceptions, and knowing when to skip exceptions entirely with TryParse .
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free C# course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
An exception is a circuit breaker in your house. When something goes wrong — a short circuit, too much load — the breaker trips instead of letting the whole house burn down. The try block is the wiring that might fault; the catch block is the breaker that trips and lets you respond calmly; the finally block is the safety routine that runs whether or not anything tripped. Without it, one bad value (a user typing "abc" where you expected a number) takes the entire program down.
Every one of these inherits from Exception , which is why a single catch (Exception ex) can catch them all — but, as you'll see, catching the specific type is usually better.
1. try / catch / finally
An exception is C#'s way of saying "I can't do this" at runtime — like parsing the text "abc" as a number. Left alone, it crashes your program. You put the risky code inside a try block; if it throws, C# jumps to a matching catch block instead of crashing. The optional finally block runs no matter what — perfect for cleanup. Read this worked example, run it, then you'll write your own.
Your turn. The program below would crash because "oops" isn't a number. Wrap it in a try/catch so it fails gracefully — fill in the blanks marked ___ using the hints.
2. Catching Specific Exceptions
Catching the broad Exception works, but it treats every problem the same. Usually you want to react differently to different errors — a bad index isn't the same as bad text. So you list several catch blocks, each for a specific type. The rule that trips everyone up: specific catches must come before the general one . C# checks them top to bottom and stops at the first match, so if catch (Exception) came first it would swallow everything and the compiler rejects the unreachable ones below it.
Now you try. Dividing an int by zero throws a specific exception. Catch exactly that type, then add a finally block that always runs.
3. Throwing & Re-throwing
You don't only catch exceptions — you can throw them too. When a method is handed input it can't work with, the cleanest response is throw new ArgumentException("...") so the caller knows immediately. If you catch an exception just to log it but can't fully handle it, re-throw with a bare throw; . Crucially, throw; keeps the original stack trace (the breadcrumb trail showing where the error really started); writing throw ex; instead resets it and hides the true source.
When none of the built-in types describe your problem well, make your own. A custom exception is just a class that inherits from Exception . The big win is that it can carry extra data — here, exactly how much money is missing — so the catch block can give a genuinely helpful message instead of a generic one.
Name custom exceptions ending in Exception by convention, and only create them when a built-in type genuinely doesn't fit.
4. When NOT to Use Exceptions
Exceptions are for the unexpected . A user typing letters into an age box is completely expected, so reaching for try/catch there is the wrong tool — it's slower and noisier. Use TryParse , which returns true / false instead of throwing. Separately, when you open something that must be closed (files, streams, connections), wrap it in a using block: it calls Dispose() for you automatically, even if an exception is thrown — the tidy version of a try/finally cleanup.
Q: Should I just wrap my whole program in one big try/catch?
No. A giant catch hides where the error came from and tempts you to ignore it. Catch close to the risky operation, catch the specific type, and only handle what you can actually recover from.
Q: When do I use TryParse instead of try/catch ?
Whenever failure is expected — typically user input or file content. Throwing and catching is slow; TryParse just returns false . Save exceptions for genuinely unexpected situations.
Q: What's the difference between throw; and throw ex; ?
throw; re-throws the current exception and preserves the original stack trace (where it really started). throw ex; throws it again but resets the trace to this line, hiding the source. Almost always use throw; .
Q: Does finally run even if there's a return in the try ?
Yes. finally runs whether the try finishes normally, throws, or returns early. That guarantee is exactly why it's the right place for cleanup.
No blanks this time — just a brief and an outline. Combine everything: parse user input safely , then divide inside a try/catch so neither bad text nor a zero can crash you. Run it with "4" , "0" , and "abc" to check all three paths.
Practice quiz
What goes inside a try block?
- Cleanup code that must always run
- The catch handler
- Code that might throw an exception
- The exception's message
Answer: Code that might throw an exception. A try block holds the risky code that might throw; if it throws, control jumps to a matching catch.
When does a finally block run?
- Always — whether or not an exception occurred
- Only when an exception is thrown
- Only when no exception is thrown
- Only if you call it explicitly
Answer: Always — whether or not an exception occurred. finally always runs — crash or no crash, even after a return in the try — which makes it ideal for cleanup.
How must you order multiple catch blocks?
- General before specific
- Alphabetically
- Order doesn't matter
- Specific before general
Answer: Specific before general. Specific catches must come before the general catch (Exception); C# checks top to bottom and a general catch first would make the rest unreachable (CS0160).
What does ex.Message give you?
- The line number
- A human-readable description of what went wrong
- The exception type name only
- The full stack trace
Answer: A human-readable description of what went wrong. ex.Message is the human-readable description of the error carried on the exception object.
Which exception is thrown by ?
- FormatException
- IndexOutOfRangeException
- DivideByZeroException
- NullReferenceException
Answer: FormatException. Parsing text that isn't a valid number throws FormatException.
Which exception is thrown by dividing an int by zero ( )?
- ArgumentException
- FormatException
- DivideByZeroException
- InvalidOperationException
Answer: DivideByZeroException. Dividing an int by zero throws DivideByZeroException.
What is the difference between and in a catch block?
- They are identical
- throw; preserves the original stack trace; throw ex; resets it
- throw ex; preserves the stack trace; throw; resets it
- throw; rethrows a new exception type
Answer: throw; preserves the original stack trace; throw ex; resets it. A bare throw; re-throws the current exception and keeps the original stack trace; throw ex; resets the trace to that line, hiding the source.
How do you create a custom exception?
- Implement IException
A custom exception is just a class that inherits from Exception, and it can carry extra data for the caller.
For expected failures like bad user input, what should you use instead of try/catch?
- A bigger try block
- int.TryParse (returns true/false)
- throw new Exception
- A finally block
Answer: int.TryParse (returns true/false). TryParse returns true/false instead of throwing — far faster and cleaner than try/catch for expected failures.
What does a block guarantee for a disposable resource?
- It catches all exceptions
- It retries the operation
- It calls Dispose() automatically, even if an exception is thrown
- It logs the error
Answer: It calls Dispose() automatically, even if an exception is thrown. A using block calls Dispose() for you when the block ends, even on error — the tidy alternative to a try/finally that closes resources.