Exception Handling in C#: Your Safety Net for Code Adventures
The Story of the Safety Net
Imagine you’re a tightrope walker at a circus. You’re walking high above the ground, balancing carefully. But wait—what if you slip? That’s where the safety net comes in! It catches you when things go wrong, so you don’t crash to the ground.
In C#, exception handling is your code’s safety net. When something unexpected happens—like trying to divide by zero or opening a file that doesn’t exist—exceptions catch the problem before your program crashes.
What is an Exception?
Think of exceptions like warning alarms in your house.
- Smoke detector beeps? Fire exception!
- Water sensor goes off? Flood exception!
- Door alarm triggers? Intruder exception!
Each alarm tells you something specific went wrong, so you can respond appropriately.
// This will trigger an exception!
int result = 10 / 0;
// DivideByZeroException alarm!
Try-Catch-Finally: Your Three-Part Safety System
The Magic Trio
Picture a superhero team:
- TRY = The brave explorer who attempts dangerous missions
- CATCH = The rescuer who saves the day when things go wrong
- FINALLY = The cleanup crew who always tidies up, no matter what
try
{
// TRY: Attempt something risky
int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
// CATCH: Handle the problem
Console.WriteLine("Oops! Can't divide by zero!");
}
finally
{
// FINALLY: Always runs
Console.WriteLine("Cleaning up...");
}
How It Flows
graph TD A[Start TRY block] --> B{Exception occurs?} B -->|Yes| C[Jump to CATCH] B -->|No| D[Skip CATCH] C --> E[FINALLY runs] D --> E E --> F[Continue program]
Real-World Example
try
{
// Try to open a file
string text = File.ReadAllText("diary.txt");
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("Diary not found!");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("No permission!");
}
finally
{
Console.WriteLine("Done trying.");
}
Throwing Exceptions: Sounding Your Own Alarm
Sometimes YOU need to raise the alarm! Like a lifeguard blowing a whistle when someone breaks the rules.
The throw Keyword
void SetAge(int age)
{
if (age < 0)
{
throw new ArgumentException(
"Age cannot be negative!");
}
Console.WriteLine(quot;Age set to {age}");
}
Re-throwing Exceptions
Sometimes you catch a problem, do something with it, then pass it along:
try
{
ProcessOrder();
}
catch (Exception ex)
{
LogError(ex); // Write to log
throw; // Re-throw same exception
}
Common Exception Types: Meet the Usual Suspects
Like different types of emergencies need different responses, C# has specific exception types:
| Exception | What Went Wrong | Example |
|---|---|---|
NullReferenceException |
Used something that doesn’t exist | Calling method on null |
ArgumentException |
Bad input given | Negative age |
ArgumentNullException |
Null passed where not allowed | Null name |
InvalidOperationException |
Wrong time to do something | Reading empty list |
IndexOutOfRangeException |
Array index too big/small | arr[100] when arr has 5 items |
FileNotFoundException |
File doesn’t exist | Opening “ghost.txt” |
DivideByZeroException |
Divided by zero | 10 / 0 |
Examples in Action
// NullReferenceException
string name = null;
int length = name.Length; // BOOM!
// IndexOutOfRangeException
int[] numbers = {1, 2, 3};
int x = numbers[10]; // BOOM!
// ArgumentNullException
void Greet(string name)
{
if (name == null)
throw new ArgumentNullException(
nameof(name));
}
Custom Exceptions: Creating Your Own Alarms
Sometimes the built-in exceptions don’t fit. Time to create your own!
Recipe for Custom Exceptions
- Create a class that inherits from
Exception - Add your special message or properties
- Include the standard constructors
public class InsufficientFundsException
: Exception
{
public decimal Balance { get; }
public decimal Amount { get; }
public InsufficientFundsException(
decimal balance,
decimal amount)
: base(quot;Need {amount}, have {balance}")
{
Balance = balance;
Amount = amount;
}
}
Using Your Custom Exception
void Withdraw(decimal amount)
{
if (amount > balance)
{
throw new InsufficientFundsException(
balance, amount);
}
balance -= amount;
}
Exception Filters: The Smart Catcher
Exception filters let you be picky about which exceptions you catch. Like a bouncer at a club checking IDs!
The when Keyword
try
{
ProcessData();
}
catch (Exception ex)
when (ex.Message.Contains("timeout"))
{
Console.WriteLine("Timeout! Retry...");
}
catch (Exception ex)
when (ex.Message.Contains("permission"))
{
Console.WriteLine("Access denied!");
}
Filter by Property
try
{
MakeWebRequest();
}
catch (HttpRequestException ex)
when (ex.StatusCode == 404)
{
Console.WriteLine("Page not found!");
}
catch (HttpRequestException ex)
when (ex.StatusCode == 500)
{
Console.WriteLine("Server error!");
}
Why Filters Are Amazing
graph TD A[Exception Thrown] --> B{Filter 1 matches?} B -->|Yes| C[Catch Block 1] B -->|No| D{Filter 2 matches?} D -->|Yes| E[Catch Block 2] D -->|No| F[Exception bubbles up]
Exception Best Practices: The Golden Rules
1. Catch Specific Exceptions First
// GOOD: Specific to general
try { /* code */ }
catch (FileNotFoundException ex) { }
catch (IOException ex) { }
catch (Exception ex) { }
// BAD: General catches everything
try { /* code */ }
catch (Exception ex) { } // Too greedy!
2. Don’t Catch and Ignore
// BAD: Swallowing exceptions
try { /* code */ }
catch { } // Silent failure! Dangerous!
// GOOD: At least log it
try { /* code */ }
catch (Exception ex)
{
Logger.LogError(ex);
}
3. Use finally for Cleanup
FileStream file = null;
try
{
file = File.Open("data.txt",
FileMode.Open);
// Process file
}
finally
{
file?.Close(); // Always closes!
}
4. Throw with Meaningful Messages
// BAD
throw new Exception("Error");
// GOOD
throw new ArgumentException(
quot;Age must be positive, got: {age}",
nameof(age));
5. Preserve the Stack Trace
// BAD: Loses original stack trace
catch (Exception ex)
{
throw ex; // Stack trace reset!
}
// GOOD: Preserves stack trace
catch (Exception ex)
{
throw; // Original trace kept!
}
6. Use Exception Types Correctly
graph TD A[Choose Exception Type] --> B{Is input invalid?} B -->|Yes| C[ArgumentException] B -->|No| D{Is state wrong?} D -->|Yes| E[InvalidOperationException] D -->|No| F{Is it not found?} F -->|Yes| G[KeyNotFoundException] F -->|No| H[Consider custom exception]
Putting It All Together
Here’s a complete example showing everything working together:
public class BankAccount
{
private decimal balance;
public void Withdraw(decimal amount)
{
// Validate input
if (amount <= 0)
throw new ArgumentException(
"Amount must be positive",
nameof(amount));
// Check business rule
if (amount > balance)
throw new InsufficientFundsException(
balance, amount);
try
{
// Try the operation
ProcessWithdrawal(amount);
balance -= amount;
}
catch (Exception ex)
when (ex is TimeoutException)
{
// Handle timeout specifically
Logger.Log("Timeout during withdrawal");
throw;
}
finally
{
// Always audit
AuditLog.Record("Withdrawal attempt");
}
}
}
Summary: Your Exception Handling Toolkit
| Tool | Purpose |
|---|---|
try |
Wrap risky code |
catch |
Handle specific errors |
finally |
Cleanup that always runs |
throw |
Raise exceptions |
when |
Filter which exceptions to catch |
| Custom exceptions | Domain-specific errors |
Remember: Exceptions are your friends! They tell you exactly what went wrong and where. Embrace them, handle them wisely, and your code will be rock-solid!
“A program that handles exceptions well is like a ship that can weather any storm.” - Every wise developer ever
You’ve now mastered exception handling in C#! Your code has a safety net, and you know exactly how to use it. Go forth and write robust, unbreakable programs!