Chapter 16: Exception Handling
1. What is an Exception? (Super Simple Analogy)
An exception is like an emergency alarm that goes off when something unexpected happens:
- You try to divide by zero → “Hey! You can’t do that!”
- You try to open a file that doesn’t exist → “File not found!”
- You try to parse “abc” as a number → “That’s not a number!”
Instead of crashing, the program throws an exception (like raising a red flag), and you can catch it and decide what to do.
2. The try-catch-finally Block – The Core of Exception Handling
Syntax:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
try { // Code that might cause an exception } catch (ExceptionType ex) // You can have multiple catch blocks { // Handle the error gracefully Console.WriteLine("Oops! Something went wrong: " + ex.Message); } finally { // This ALWAYS runs – even if there was an exception or return/break Console.WriteLine("Cleaning up resources..."); } |
Real example – Dividing two numbers safely
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
Console.Write("Enter first number: "); string num1Str = Console.ReadLine(); Console.Write("Enter second number: "); string num2Str = Console.ReadLine(); try { int num1 = int.Parse(num1Str); int num2 = int.Parse(num2Str); int result = num1 / num2; // This can throw DivideByZeroException! Console.WriteLine($"Result: {num1} / {num2} = {result}"); } catch (FormatException ex) { Console.WriteLine("Error: Please enter valid numbers! 😕"); } catch (DivideByZeroException ex) { Console.WriteLine("Error: Cannot divide by zero! 🚫"); } catch (Exception ex) // Catch-all for any other unexpected error { Console.WriteLine($"Unexpected error: {ex.Message}"); } finally { Console.WriteLine("Thank you for using the calculator! 👋"); } |
Output examples:
- User enters “10” and “2” → “Result: 10 / 2 = 5”
- User enters “abc” → “Error: Please enter valid numbers!”
- User enters “10” and “0” → “Error: Cannot divide by zero!”
- In all cases → “Thank you for using the calculator!” (finally runs)
3. Throwing Exceptions Yourself (When You Want to Signal an Error)
You can throw exceptions manually using the throw keyword.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
class BankAccount { private decimal _balance; public decimal Balance => _balance; public void Withdraw(decimal amount) { if (amount <= 0) { throw new ArgumentException("Withdrawal amount must be positive!"); } if (amount > _balance) { throw new InvalidOperationException("Insufficient funds!"); } _balance -= amount; Console.WriteLine($"Withdrew ₹{amount:N2}. New balance: ₹{_balance:N2}"); } } // Usage var account = new BankAccount { _balance = 1000 }; try { account.Withdraw(1500); // Will throw InvalidOperationException } catch (InvalidOperationException ex) { Console.WriteLine($"Bank says: {ex.Message}"); } catch (ArgumentException ex) { Console.WriteLine($"Invalid input: {ex.Message}"); } |
4. Creating Custom Exceptions (Professional & Meaningful)
Sometimes built-in exceptions like ArgumentException are not specific enough. You should create your own exception classes for your domain.
Best practice:
- Inherit from Exception (or more specific like ApplicationException)
- Add custom properties if needed
- Provide good messages and constructors
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// Custom exception class InsufficientFundsException : Exception { public decimal CurrentBalance { get; } public decimal RequestedAmount { get; } public InsufficientFundsException(decimal balance, decimal requested) : base($"Insufficient funds! You have ₹{balance:N2}, but tried to withdraw ₹{requested:N2}.") { CurrentBalance = balance; RequestedAmount = requested; } } class BankAccount { private decimal _balance = 5000; public void Withdraw(decimal amount) { if (amount > _balance) { throw new InsufficientFundsException(_balance, amount); } _balance -= amount; Console.WriteLine($"Withdrew ₹{amount:N2}. Remaining: ₹{_balance:N2}"); } } // Usage var account = new BankAccount(); try { account.Withdraw(10000); } catch (InsufficientFundsException ex) { Console.WriteLine(ex.Message); Console.WriteLine($"You only have ₹{ex.CurrentBalance:N2} available."); } catch (Exception ex) { Console.WriteLine($"Unexpected error: {ex.Message}"); } |
Output:
|
0 1 2 3 4 5 6 7 |
Insufficient funds! You have ₹5,000.00, but tried to withdraw ₹10,000.00. You only have ₹5,000.00 available. |
5. Best Practices for Exception Handling (2026 Style)
- Catch specific exceptions first, then general Exception last
- Never catch Exception unless you really need to (and log it!)
- Don’t swallow exceptions (never do catch { } empty)
- Use finally for cleanup (closing files, connections, etc.)
- Throw exceptions early (fail fast)
- Create custom exceptions for your business domain
- Log exceptions (use ILogger in real apps)
Mini-Project: Safe File Reader with Exception Handling
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class Program { static void Main() { Console.Write("Enter file path to read: "); string filePath = Console.ReadLine(); try { string content = File.ReadAllText(filePath); Console.WriteLine("\nFile contents:"); Console.WriteLine(content); } catch (FileNotFoundException) { Console.WriteLine($"Error: File '{filePath}' not found! 😕"); } catch (UnauthorizedAccessException) { Console.WriteLine("Error: You don't have permission to read this file! 🔒"); } catch (IOException ex) { Console.WriteLine($"IO error: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"Unexpected error: {ex.Message}"); // In real app: log the exception! } finally { Console.WriteLine("\nOperation finished. Thank you! 👋"); } } } |
Summary – What We Learned Today
- try-catch-finally → catch and handle errors gracefully
- Multiple catch blocks → handle specific exceptions first
- throw → manually signal an error
- Custom exceptions → make errors meaningful for your domain
- finally → always runs (cleanup)
- Best practice → catch specific → general → log → don’t swallow
Your Homework (Super Practical!)
- Create a new console project called ExceptionMaster
- Create a class SafeCalculator with methods:
- Divide(int a, int b) → throw DivideByZeroException if b == 0
- ParseNumber(string input) → throw custom InvalidNumberFormatException if not a number
- Create your own custom exception: InvalidNumberFormatException
- In Program.cs: Ask user for two numbers and operation (+, -, *, /), use try-catch to handle all possible errors nicely
Next lesson: Generics – we’re going to learn how to write super reusable, type-safe code that works with any data type!
You’re doing absolutely fantastic! 🎉 Any part confusing? Want more examples with custom exceptions or finally blocks? Just tell me — I’m right here for you! 💙
