1. Introduction
Imagine you're writing a calculator to divide two numbers. Everything works great until someone tries to divide by zero:
int a = 10;
int b = 0;
int result = a / b; // Boom!
At this point, your program "crashes" and the user sees a scary error message. That's what we call an exceptional situation — something that shouldn't happen in a "normal" program flow and needs special attention.
Exception — is a special mechanism that .NET uses to tell your program that something went off the rails, and execution should be stopped or handled in a special way.
When a runtime error happens (like dividing by zero, trying to access a missing file, or — our old friend — dereferencing null), .NET "throws" (throw) a special exception object. This kicks off a search for a handler that can "catch" (catch) the situation and knows what to do about it.
Why not just return an "error code"?
You could return an error code — and a lot of old-school languages (Pascal, C, and even some modern APIs) do just that. But it's awkward and risky: it's easy to forget to check the error code (we all hope for the best, right?) — and it's super hard to figure out where exactly things went wrong. Exceptions let you track any errors in one place and react flexibly, without cluttering your main code with a bunch of checks.
2. "Throwing" an Exception
Exceptions can pop up automatically — when the runtime hits an error, or you can throw them yourself using the throw keyword:
int[] arr = new int[2];
arr[10] = 5; // exception will be thrown automatically System.IndexOutOfRangeException
throw new Exception("Absolute catastrophe!"); // manually throwing an Exception
Even if you think your program is perfectly designed, something unexpected can always happen: the user closed a file, the internet died, someone unplugged the computer (well, almost).
Luckily, .NET gave us not just a way to "throw" exceptions, but also tools to catch and handle them — we'll get to those in a bit.
3. Exception Lifecycle: from throw to catch
When something goes wrong in your code, .NET kicks off an "emergency procedure":
- Creates an exception object (like IndexOutOfRangeException).
- Throws it using the throw keyword.
- Looks for a handler: walks up the call stack (from the current method to the caller) — looking for the first catch block that matches the type of the thrown exception.
- If a handler is found — the program keeps running inside the catch block.
- If no handler is found — the program crashes and prints the "call stack" with error details.
It's kinda like a ball bouncing up a staircase — until it finds a catcher, it can keep "bouncing" up the stack.
4. Exception Hierarchy in .NET
The "Parent and Child" Tree
In .NET (like in most OOP languages), exceptions are implemented as classes that inherit from each other and form a whole hierarchy.
They all share a common ancestor — the base class System.Exception.
System.Object
└─ System.Exception
├─ System.SystemException
│ ├─ System.NullReferenceException
│ ├─ System.IndexOutOfRangeException
│ ├─ System.DivideByZeroException
│ ├─ System.OutOfMemoryException
│ └─ ... and many others
├─ System.IO.IOException
│ ├─ System.IO.FileNotFoundException
│ ├─ System.IO.DirectoryNotFoundException
│ └─ ...
├─ System.ArgumentException
│ ├─ System.ArgumentNullException
│ └─ System.ArgumentOutOfRangeException
└─ (your own Exception classes)
Why classes?
Thanks to the class hierarchy, you can catch a whole "class" of problems at once — for example, all errors related to function arguments (ArgumentException and its descendants). Plus, it lets you add your own custom error types (more on that in the next lectures).
The Most Popular .NET Exceptions
- NullReferenceException — trying to access a method or property of an object that's null. Old friend!
- DivideByZeroException — dividing by zero.
- IndexOutOfRangeException — array index out of bounds.
- ArgumentException, ArgumentNullException, ArgumentOutOfRangeException — invalid method parameters.
- FileNotFoundException, IOException — file handling problems.
- InvalidOperationException — operation isn't possible in the current object state.
- FormatException — invalid data format (like trying to convert "abcd" to a number).
5. Examples: How Different Exceptions "Fire Off"
Let's check out some code examples that trigger different exceptions.
Example 1: NullReferenceException
string? str = null;
Console.WriteLine(str.Length); // Here's the "Boom!" — str is null
Output:
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
Example 2: DivideByZeroException
int a = 42;
int b = 0;
int c = a / b; // Danger! Division by 0
Output:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
Example 3: IndexOutOfRangeException
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[5]); // No element with index 5
There are obviously extra indices here, and the output is:
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
Example 4: FileNotFoundException
using System.IO;
string content = File.ReadAllText("secret.txt");
If you don't have a file named "secret.txt", you'll get something like this:
Unhandled exception. System.IO.FileNotFoundException: Could not find file '/Users/zapp/RiderProjects/ConsoleApp1/ConsoleApp1/bin/Debug/net9.0/secret.txt'.
Example 5: FormatException
string input = "thirteen";
int number = int.Parse(input); // String is not a number
Output:
Unhandled exception. System.FormatException: The input string 'thirteen' was not in a correct format.
6. How to Use What You Learned?
Understanding how exceptions work is actually super important for any developer. It's not just "theory for theory's sake" — it's a practical skill that directly affects how stable your programs are. When you know how to handle errors right, your program won't "crash" at the first sign of trouble, but instead — will calmly and clearly tell the user what went wrong. That makes it more reliable, more professional, and just friendlier to people.
And it's not just about convenience. Interviewers love to ask about how exception handling works, what types of Exception there are, and how the whole mechanism works in .NET. So knowing this stuff is a big plus for your job search karma.
Plus, you'll run into exceptions all the time in real projects. Working with files, networks, databases, third-party libraries and frameworks — all of that uses the exception system a lot. So it's better to figure out how it works now, so you don't get surprised later.
7. Typical Mistakes When Working with Exceptions
Mistake #1: Ignoring exceptions altogether.
Some newbies (and sometimes even experienced devs) treat exceptions like something annoying or in the way. They either totally ignore them, or even worse, wrap code in catch { } and do nothing. As a result, the program "silently" keeps running in a weird state, and it's impossible to track down what went wrong.
Mistake #2: Mixing up exceptions and regular errors.
People often forget that not every error should be caught with try-catch. For example, if a user enters invalid data, it's better to check it manually first, instead of hoping for a FormatException — and definitely don't use exceptions as your main logic. Exceptions are for exceptional cases, not for routine stuff.
Mistake #3: Not understanding the exception hierarchy.
Sometimes people try to catch too broad a class (Exception) and end up catching everything, including errors that should probably be left unhandled. And sometimes the opposite — they catch only super specific types (IndexOutOfRangeException, NullReferenceException), forgetting that other stuff can happen too. It's important to understand how the exception hierarchy in .NET works and what exactly you want to handle.
And most importantly — remember: exceptions in C# aren't a disaster or a "game over" signal, they're just a way to switch to a special execution scenario. It's a powerful tool, if you use it right.
GO TO FULL VERSION