What is exception handling and how does it work in C#?

8 minbeginner.NETexceptionserror-handling

Quick Answer

Exception handling allows graceful handling of runtime errors using try-catch-finally blocks. When an error occurs, an exception object is created and the runtime searches for appropriate handlers. Use specific exception types, avoid catching and ignoring exceptions, use 'throw' instead of 'throw ex' to preserve stack trace, and include cleanup code in finally blocks.

Detailed Answer

Exception Handling is a mechanism in C# that allows you to handle runtime errors gracefully, preventing your application from crashing and providing meaningful error messages to users.

How Exception Handling Works:

  • When an error occurs, an exception object is created
  • The runtime searches for an appropriate exception handler
  • If found, the handler executes and the program continues
  • If not found, the program terminates with an unhandled exception

Basic Exception Handling Structure:

try
{
    // Code that might throw an exception
    int result = Divide(10, 0);
}
catch (DivideByZeroException ex)
{
    // Handle specific exception
    Console.WriteLine($"Error: {ex.Message}");
}
catch (Exception ex)
{
    // Handle any other exception
    Console.WriteLine($"Unexpected error: {ex.Message}");
}
finally
{
    // Always executes (cleanup code)
    Console.WriteLine("Cleanup code here");
}

Exception Hierarchy:

System.Object
└── System.Exception
    ├── System.SystemException
    │   ├── ArgumentException
    │   ├── NullReferenceException
    │   ├── IndexOutOfRangeException
    │   └── DivideByZeroException
    └── System.ApplicationException
        └── Custom exceptions

Key Differences: throw vs throw ex vs throw new

1. throw (rethrow):

try
{
    // Some operation
}
catch (Exception ex)
{
    // Log the exception
    LogError(ex);
    
    // Rethrow the original exception (preserves stack trace)
    throw;  // GOOD - keeps original stack trace
}

2. throw ex (lose stack trace):

try
{
    // Some operation
}
catch (Exception ex)
{
    // Log the exception
    LogError(ex);
    
    // Rethrow but lose original stack trace
    throw ex;  // BAD - loses original stack trace
}

3. throw new (new exception):

try
{
    // Some operation
}
catch (Exception ex)
{
    // Create new exception with original as inner exception
    throw new CustomException("Something went wrong", ex);  // GOOD
}

When to Use Each:

  • try-catch: Handle exceptions you can recover from
  • try-finally: Ensure cleanup code always runs
  • try-catch-finally: Handle exceptions AND ensure cleanup

Custom Exceptions:

// Custom exception class
public class InsufficientFundsException : Exception
{
    public decimal CurrentBalance { get; }
    public decimal RequiredAmount { get; }
    
    public InsufficientFundsException(decimal currentBalance, decimal requiredAmount)
        : base($"Insufficient funds. Current: {currentBalance:C}, Required: {requiredAmount:C}")
    {
        CurrentBalance = currentBalance;
        RequiredAmount = requiredAmount;
    }
    
    public InsufficientFundsException(string message, Exception innerException)
        : base(message, innerException)
    {
    }
}

// Usage
public void Withdraw(decimal amount)
{
    if (amount > Balance)
    {
        throw new InsufficientFundsException(Balance, amount);
    }
    
    Balance -= amount;
}

Best Practices:

  1. Catch specific exceptions when possible
  2. Don't catch and ignore exceptions silently
  3. Use throw instead of throw ex to preserve stack trace
  4. Include inner exceptions when creating new exceptions
  5. Log exceptions before rethrowing
  6. Use finally blocks for cleanup code
  7. Don't throw exceptions for normal program flow

Related Resources