1. Introduction
In a perfect world, code should be perfect. But unfortunately, even the most careful programmer will eventually run into unexpected situations: file not found, user entered a string instead of a number, the network suddenly stopped working, or something came back from the database not as expected.
If you don't catch these problems, the program will just "crash," sometimes with a mysterious error message and a stack trace. That's not what your user or future support team wants to see. You need to learn how to "catch" these errors and react to them: gracefully shut down, show a human-friendly message, or even fix the situation on the fly.
try-catch is a way to tell the compiler and runtime: "Try to run this chunk of code. If something goes wrong, don't freak out, give me a chance to handle it!"
2. Basic try-catch Syntax
The general structure is super simple:
try
{
// Here we write dangerous (or potentially dangerous) code
}
catch (ExceptionType variableName)
{
// Here we write what to do if an exception of type ExceptionType occurs
}
- The try block is the "danger zone." Put any operations in here that could potentially "blow up."
- The catch block catches exceptions of a specific type. If an exception happens inside try, control jumps straight to the first matching catch, and any code after the "problem spot" in try won't run.
Example: Simple Exception Handling
Let's say our mini-calculator from previous lectures can now divide numbers. But the user might enter zero! Let's see how this works without error handling:
int a = 10;
int b = 0;
int result = a / b; // BOOM! System.DivideByZeroException
Console.WriteLine(result);
The program will crash with a DivideByZeroException error:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
Now let's "fix" it using try-catch:
int a = 10;
int b = 0;
try
{
int result = a / b; // dangerous operation
Console.WriteLine("Division result: " + result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Oops! Division by zero is not possible: " + ex.Message);
}
Now the program won't "crash." The console will show a message about division by zero:
Oops! Division by zero is not possible: Attempted to divide by zero.
3. How try-catch Works: Step by Step
Actually, you can have as many lines of code as you want inside try. If any action inside the block throws an exception, control immediately jumps to the nearest matching catch. Everything written after the error spot in the try block won't run.
Let's look at a bigger example:
try
{
Console.WriteLine("Starting...");
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[1]); // this is fine
Console.WriteLine(numbers[5]); // error: index out of array bounds
Console.WriteLine("This message will never show up!");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Error: Tried to access a non-existent array element");
}
The console output will be:
Starting...
2
Error: Tried to access a non-existent array element
Process finished with exit code 0.
How does this program work?
- It prints "Starting...".
- Then it tries to print numbers[1] — that's 2.
- When it gets to numbers[5], it "crashes" and throws IndexOutOfRangeException.
- The program immediately jumps to the catch block, skipping Console.WriteLine("This message will never show up!");
- The console shows our message from catch.
4. Can You Handle Different Types of Errors Differently?
Yep! You can specify multiple catch blocks to react differently to different types of exceptions. Super handy: for example, if one action is about reading a file (could be FileNotFoundException), and another is about division (could be DivideByZeroException).
try
{
// Your dangerous code
}
catch (DivideByZeroException)
{
Console.WriteLine("Division by zero error");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Error: array index out of range");
}
catch (Exception ex)
{
Console.WriteLine("Some unexpected error: " + ex.Message);
}
Here, the last catch block with type Exception is the "universal catcher." It'll catch any other exceptions that didn't match the previous blocks. But remember: if you put it first, the others will never be reached! Always put the "broad" catch at the end.
5. What Does the Exception Object Look Like?
Inside the catch block, you can optionally specify a variable — like catch (Exception ex). It contains all the info about the error: message, error type, stack trace, inner exceptions.
Here's a quick example:
try
{
string? text = null;
Console.WriteLine(text.Length); // Oops! NullReferenceException
}
catch (NullReferenceException ex)
{
Console.WriteLine("Caught exception: " + ex.Message);
Console.WriteLine("Stack trace: " + ex.StackTrace);
}
This approach is a lifesaver when debugging tricky errors: you'll always be able to see where and why your program "tripped up."
6. Catching an Error and Continuing
In real, especially user-facing, apps, an error isn't the end of the world. So the user entered the wrong file path — it happens. That's no reason to reboot the program in a panic. Instead, we calmly ask them to try again.
The example below shows a simple but effective approach: we wrap file reading in a loop, and if something goes wrong, we just show an error and ask them to try again. No panic, no red screens.
bool success = false;
while (!success)
{
Console.Write("Enter file name: ");
string fileName = Console.ReadLine() ?? "";
try
{
string content = File.ReadAllText(fileName);
Console.WriteLine("File read successfully!");
success = true; // YAY!
}
catch (FileNotFoundException)
{
Console.WriteLine("Error: file not found. Please try again.");
}
}
The key idea here is not just to catch the exception, but to handle it smartly: we don't let the program crash, we switch it to "let's try again, but right this time" mode. The user gets a clear hint, the program gets a second chance, and you get gratitude and respect.
7. Typical Mistakes When Working with try-catch
Mistake #1: Wrapping all your code in one universal try-catch.
It looks tempting — one try, one catch (Exception), and it seems like "everything's protected." But in reality, you lose control over the situation. You have no idea where exactly the failure happened, or what error you're actually reacting to. Instead of reliability, you get total loss of diagnostics.
Mistake #2: Catching exceptions and doing nothing with them.
Real-life example:
try
{
// something potentially dangerous
}
catch
{
// silence...
}
This code literally says: "Something went wrong... oh well." That's dangerous: the program might keep running in a broken state, and you'll never even know there was an error. Always at least print a message or log the failure. No empty catch blocks!
Mistake #3: Catching too broad an exception type.
If you catch Exception when you know you might get, say, FormatException or FileNotFoundException, you lose the chance to react properly to a specific problem. The narrower and more precise your catch, the more predictable and smart your program behaves in weird situations.
8. When NOT to Use Exceptions
Sometimes beginners start using exceptions... as a regular way to control logic, for example:
try
{
if (x < 0) throw new Exception("x must be >= 0");
}
catch (Exception)
{
// rollback state, keep going...
}
Don't do this! Exceptions are a tool for unexpected, rare situations (file read error, network failure, etc.), not for regular user data validation. For those cases, use conditional statements (if, else) and return special values if something's wrong.
GO TO FULL VERSION