Roadmap › C# Language — Basics to Advanced › Module: Errors, Resources and Memory
Lessons in this course (38)

28. Built-in exceptions, throw and custom exceptions

Basic★ Must knowLesson 28 of 38

Exception hierarchy: everything derives from System.Exception. Common built-in exceptions:

ExceptionThrown when
NullReferenceExceptionYou use a member of a null reference
ArgumentException / ArgumentNullException / ArgumentOutOfRangeExceptionA method receives an invalid argument (throw these yourself to validate input)
InvalidOperationExceptionThe object's state doesn't allow the call (e.g. modifying a list during foreach, First() on empty sequence)
IndexOutOfRangeExceptionArray index outside its bounds
KeyNotFoundExceptionDictionary indexer with a missing key
InvalidCastExceptionBad explicit cast / unboxing to the wrong type
FormatExceptionint.Parse("abc")
OverflowExceptionchecked arithmetic overflows
DivideByZeroExceptionInteger division by zero (double gives Infinity instead)
FileNotFoundException, DirectoryNotFoundException, IOExceptionFile system problems
NotImplementedException / NotSupportedExceptionMethod not written yet / operation not supported by design
TimeoutException, OperationCanceledException / TaskCanceledExceptionTimeouts and cancellation
HttpRequestExceptionHttpClient call failed
OutOfMemoryException, StackOverflowExceptionRuntime failures — the latter can't be caught and kills the process

Throwing:

csharp
public void Withdraw(decimal amount)
{
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);            // .NET 8 throw helpers
    if (amount > Balance) throw new InsufficientFundsException(Balance, amount);
    Balance -= amount;
}

public Customer(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));   // throw expression

try { Save(); }
catch (SqlException ex)
{
    log.LogError(ex, "Save failed");
    throw;                  // ✅ rethrow keeps the original stack trace
    // throw ex;            // ❌ resets the stack trace to this line
}

Custom exception:

csharp
public class InsufficientFundsException : Exception
{
    public decimal Balance { get; }
    public decimal Requested { get; }

    public InsufficientFundsException() { }
    public InsufficientFundsException(string message) : base(message) { }
    public InsufficientFundsException(string message, Exception inner) : base(message, inner) { }
    public InsufficientFundsException(decimal balance, decimal requested)
        : base($"Balance {balance} is less than requested {requested}.")
        => (Balance, Requested) = (balance, requested);
}

try { account.Withdraw(5000); }
catch (InsufficientFundsException ex) when (ex.Requested > 1000)
{
    Console.WriteLine($"Large withdrawal refused: {ex.Message}");
}

Guidelines: name ends with Exception; derive from Exception (not ApplicationException); add properties with useful data; create custom exceptions only when callers will catch them specifically — otherwise use the built-in ones.