60-Second Version: Exceptions = runtime errors. try = "attempt this". catch = "if it fails, do this instead". finally = "always do this".
1. The Problem: App Crashes
Without handling, one bad line kills everything.
Dictionary<string, Car> garage = new Dictionary<string, Car>();
garage["Kunal"] = new Car { Owner = "Kunal" };
Car car = garage["Kashvee"]; // KeyNotFoundException. App dies here.
Console.WriteLine("This never runs");
2. The Fix: try-catch
Wrap risky code. Catch the problem and recover.
try
{
Car car = garage["Kashvee"]; // Risky: Key might not exist
car.Start();
}
catch (KeyNotFoundException ex)
{
Console.WriteLine("Kashvee has no car in garage yet.");
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine("App keeps running"); // This now runs
3. finally: Always Runs
Use for cleanup. Runs if try succeeds or fails.
Car rental = null;
try
{
rental = RentCar("Kiaan");
rental.Start();
}
catch (Exception ex)
{
Console.WriteLine("Could not rent car");
}
finally
{
if(rental!= null) rental.Return(); // Always return car to lot
}
Beginner Trap: Empty catch { } hides bugs. Always log or handle the error. Silent failures are the worst bugs to debug.
1. Why Exceptions? The Real World Is Messy
Files go missing. Network fails. Users type "abc" instead of a number. You can't if check everything. Exceptions handle the "unexpected".
2. Anatomy: try, catch, finally, throw
public Car GetCar(string owner)
{
try
{
if(string.IsNullOrEmpty(owner))
throw new ArgumentException("Owner name cannot be empty"); // You throw
return garage[owner]; // System might throw KeyNotFoundException
}
catch (ArgumentException ex) // Catch specific first
{
Console.WriteLine($"Bad input: {ex.Message}");
return null;
}
catch (KeyNotFoundException ex) // Then other specifics
{
Console.WriteLine($"{owner} not found");
return null;
}
catch (Exception ex) // Catch all others last
{
Console.WriteLine($"Unknown error: {ex.Message}");
return null;
}
finally
{
LogAccess(owner); // Always runs. Good for logging, closing files
}
}
Order matters: Most specific catch first, Exception last. Otherwise specific ones never run.
3. Common Exceptions You'll See
| Exception | When It Happens | Example |
NullReferenceException | Using . on null | Car c = null; c.Start(); |
IndexOutOfRangeException | Array/List bad index | list[10] when list has 2 items |
KeyNotFoundException | Dictionary key missing | dict["Sanju"] with no Sanju |
FormatException | Bad string to number | int.Parse("abc") |
DivideByZeroException | Math error | int x = 5 / 0; |
ArgumentException | Bad method argument | You throw it for invalid input |
4. Throwing Your Own Exceptions
Use throw when your method can't continue. Don't return null silently.
public void StartCar(Car car)
{
if(car == null)
throw new ArgumentNullException(nameof(car), "Cannot start a null car");
if(car.Fuel <= 0)
throw new InvalidOperationException($"{car.Owner}'s car has no fuel");
car.Start();
}
// Calling code decides how to handle
try
{
StartCar(kaushalCar);
}
catch(InvalidOperationException ex)
{
Console.WriteLine("Go to gas station first: " + ex.Message);
}
5. Custom Exceptions: For Your App
Create your own when built-in ones don't fit. Inherit from Exception.
public class OutOfFuelException : Exception
{
public string Owner { get; }
public OutOfFuelException(string owner) : base($"{owner}'s car is out of fuel")
{
Owner = owner;
}
}
// Usage
if(prernaCar.Fuel == 0)
throw new OutOfFuelException("Prerna");
6. Best Practices: Do and Don't
Do: Catch specific exceptions. Log errors. Use finally for cleanup. Throw when you can't recover.
Don't: Use exceptions for normal flow control. Don't catch(Exception ex) { } with empty body. Don't catch and re-throw without adding value.
// Bad: Using exception for logic
try { int x = int.Parse(input); } catch { x = 0; }
// Good: Check first
if(!int.TryParse(input, out int x)) { x = 0; }
No comments yet. Be the first to share your thoughts!