How to Create Custom Exceptions in C

This article explains how to define, implement, and throw custom exception classes in C# applications. You will learn the core requirements for subclassing System.Exception, how to implement the standard constructor patterns, how to enrich your exceptions with custom properties for better debugging context, and the best practices to follow when built-in .NET exceptions are insufficient.

Why Create Custom Exceptions?

While the .NET runtime provides many built-in exceptions like ArgumentNullException and InvalidOperationException, custom exceptions allow you to capture domain-specific errors. They make error handling more granular by enabling you to catch specific application failures and attach relevant diagnostic metadata that standard exceptions do not support.

Basic Structure of a Custom Exception

To create a custom exception, create a public class that derives from System.Exception. A well-designed exception class should implement the three standard constructors provided by the base Exception class.

using System;

public class UserNotFoundException : Exception
{
    // 1. Parameterless constructor
    public UserNotFoundException() 
        : base("The requested user was not found.")
    {
    }

    // 2. Constructor accepting a custom message
    public UserNotFoundException(string message) 
        : base(message)
    {
    }

    // 3. Constructor accepting a message and an inner exception
    public UserNotFoundException(string message, Exception innerException) 
        : base(message, innerException)
    {
    }
}

Adding Custom Properties

Custom exceptions are most useful when they carry domain data. Add read-only properties initialized via the constructor to provide additional context to the consumer catching the exception.

using System;

public class InsufficientFundsException : Exception
{
    public decimal CurrentBalance { get; }
    public decimal AttemptedWithdrawal { get; }

    public InsufficientFundsException(decimal currentBalance, decimal attemptedWithdrawal)
        : base($"Cannot withdraw {attemptedWithdrawal:C}. Current balance is {currentBalance:C}.")
    {
        CurrentBalance = currentBalance;
        AttemptedWithdrawal = attemptedWithdrawal;
    }

    public InsufficientFundsException(string message, decimal currentBalance, decimal attemptedWithdrawal)
        : base(message)
    {
        CurrentBalance = currentBalance;
        AttemptedWithdrawal = attemptedWithdrawal;
    }

    public InsufficientFundsException(string message, Exception innerException)
        : base(message, innerException)
    {
    }
}

Throwing and Catching Custom Exceptions

Once defined, you can throw and catch custom exceptions using standard try-catch blocks.

public class BankAccount
{
    public decimal Balance { get; private set; } = 100.00m;

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

        Balance -= amount;
    }
}

// Handling the exception
try
{
    var account = new BankAccount();
    account.Withdraw(150.00m);
}
catch (InsufficientFundsException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
    Console.WriteLine($"Available: {ex.CurrentBalance}, Requested: {ex.AttemptedWithdrawal}");
}

Best Practices

  • Use the "Exception" Suffix: Always append Exception to your class name (e.g., OrderProcessingException).
  • Do Not Inherit from ApplicationException: Microsoft originally intended this class for custom exceptions, but modern guidelines require inheriting directly from System.Exception.
  • Provide the Standard Constructors: Always supply the default, string message, and inner exception constructors so consumers can instantiate your exception normally.
  • Make Custom Properties Read-Only: Ensure contextual properties have private or init-only setters to prevent data modification after the exception is instantiated.
  • Only Create When Necessary: Use built-in exceptions (ArgumentException, InvalidOperationException, KeyNotFoundException) if they already describe the error scenario accurately.