C# > Advanced C# > Exception Handling > Custom Exceptions

Custom Exception: InsufficientFundsException

This snippet demonstrates how to create and use a custom exception class, InsufficientFundsException, to handle specific errors related to insufficient funds in a banking scenario. It inherits from the base Exception class and adds a custom property to hold the amount of the deficiency. Proper exception handling makes code robust and maintainable.

Defining the Custom Exception

This code defines the InsufficientFundsException class. It inherits from System.Exception, allowing it to behave like a standard exception. It includes a Deficiency property to store the amount of money by which the transaction failed. Two constructors are provided: one that takes a message and the deficiency amount, and another that takes a message, the deficiency amount, and an inner exception. The inner exception is useful for wrapping lower-level exceptions that caused the insufficient funds error, preserving the original error information.

using System;

public class InsufficientFundsException : Exception
{
    public decimal Deficiency { get; private set; }

    public InsufficientFundsException(string message, decimal deficiency) : base(message)
    {
        Deficiency = deficiency;
    }

    public InsufficientFundsException(string message, decimal deficiency, Exception innerException) : base(message, innerException)
    {
        Deficiency = deficiency;
    }
}

Using the Custom Exception

This code demonstrates how to use the InsufficientFundsException. A BankAccount class is created with a Withdraw method. If the withdrawal amount exceeds the account balance, an InsufficientFundsException is thrown with a descriptive message and the deficiency amount. The Main method then calls the Withdraw method within a try-catch block. The catch block specifically handles the InsufficientFundsException, printing the error message and the deficiency amount. A generic catch block is also included to handle any other unexpected exceptions.

using System;

public class BankAccount
{
    private decimal _balance;

    public BankAccount(decimal initialBalance)
    {
        _balance = initialBalance;
    }

    public void Withdraw(decimal amount)
    {
        if (amount > _balance)
        {
            throw new InsufficientFundsException("Insufficient funds to complete the withdrawal.", amount - _balance);
        }

        _balance -= amount;
        Console.WriteLine($"Withdrawal of {amount} successful.  New balance: {_balance}");
    }

    public decimal GetBalance()
    {
        return _balance;
    }
}

public class Example
{
    public static void Main(string[] args)
    {
        BankAccount account = new BankAccount(100);

        try
        {
            account.Withdraw(150);
        }
        catch (InsufficientFundsException ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
            Console.WriteLine($"Deficiency: {ex.Deficiency}");
            // Log the exception, retry the transaction, or take other appropriate action.
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An unexpected error occurred: {ex.Message}");
            // Handle other exceptions
        }

        Console.WriteLine($"Current balance: {account.GetBalance()}");
    }
}

Concepts Behind the Snippet

Custom exceptions allow developers to create specific exception types tailored to their application's needs. This makes exception handling more precise and informative. Key concepts include: Inheritance from the Exception class, providing custom properties (like Deficiency in this case) to hold relevant data, and using specific catch blocks to handle different exception types appropriately. This increases code readability and maintainability.

Real-Life Use Case

In e-commerce applications, custom exceptions can be used to handle errors related to invalid payment methods, insufficient stock, or failed order processing. For instance, a PaymentFailedException could be thrown if a payment fails, and a OutOfStockException could be thrown if an item is no longer available. In API development, custom exceptions provide clear error responses to clients, making debugging easier and enhancing the API's usability.

Best Practices

  • Provide Meaningful Messages: Ensure exception messages are clear and informative, helping developers quickly understand the cause of the error.
  • Include Relevant Data: Include custom properties to hold data relevant to the exception, such as error codes, transaction IDs, or timestamps.
  • Use Inner Exceptions: When wrapping lower-level exceptions, preserve the original exception by passing it as the inner exception.
  • Avoid Catching Base Exception: Be specific in your catch blocks. Only catch Exception (or SystemException) as a last resort to prevent hiding unexpected errors.
  • Document Your Exceptions: Clearly document the purpose and usage of each custom exception.

Interview Tip

During interviews, be prepared to discuss the benefits of custom exceptions over general exceptions. Highlight their role in providing more specific error information, improving code clarity, and enhancing error handling strategies. Explain how custom exceptions contribute to more robust and maintainable applications.

When to Use Them

Use custom exceptions when you need to represent errors that are specific to your application's domain. These errors should be distinct from standard .NET exceptions. For instance, if you're building a banking application, you might have exceptions for insufficient funds, invalid account numbers, or transaction failures. If your code throws many general Exceptions, consider creating custom exceptions to provide more context.

Memory Footprint

The memory footprint of a custom exception is similar to that of a standard .NET exception. Each exception object consumes memory to store its message, stack trace, and any custom properties. The memory overhead is typically small but can add up if exceptions are thrown frequently. Proper exception handling, such as avoiding unnecessary exceptions and handling them efficiently, can help minimize the memory footprint.

Alternatives

Alternatives to custom exceptions include using error codes, returning nullable values or using the TryXXX pattern where a boolean is returned indicating success, and an out parameter provides the result. However, custom exceptions offer a more structured and expressive way to handle errors, particularly for complex scenarios that require detailed error information.

Pros

  • Improved Error Handling: Custom exceptions provide more specific error information, making it easier to diagnose and resolve issues.
  • Enhanced Code Readability: They make the code more readable by clearly indicating the type of error that can occur.
  • Increased Maintainability: They improve the maintainability of the code by providing a structured way to handle errors.
  • Better API Design: They provide clear error responses to clients, improving the API's usability.

Cons

  • Increased Complexity: Creating and managing custom exceptions can add complexity to the code.
  • Potential for Overuse: Overusing custom exceptions can make the code more difficult to understand.
  • Performance Overhead: Throwing exceptions can be relatively expensive in terms of performance.

FAQ

  • When should I inherit from ApplicationException instead of Exception?

    In modern .NET development, inheriting from ApplicationException is generally discouraged. It was originally intended for exceptions specific to an application, but it doesn't provide significant benefits over inheriting directly from Exception. Inheriting from Exception is the standard practice for creating custom exceptions.
  • How do I handle exceptions in asynchronous methods?

    In asynchronous methods, exceptions are propagated back to the calling thread when the Task is awaited. You can use try-catch blocks around the await keyword to handle exceptions thrown by the asynchronous method. Unhandled exceptions in asynchronous methods will crash the application.