C# > Advanced C# > Exception Handling > Throwing Exceptions

Custom Exception with Contextual Information

This snippet demonstrates how to create and throw a custom exception with additional information, providing better context for debugging and handling specific error scenarios.

Defining a Custom Exception

This code defines a custom exception class `InsufficientFundsException` that inherits from the base `Exception` class. It includes additional properties, `Balance` and `WithdrawalAmount`, to store relevant information about the failed operation. The constructor takes a message, balance, and withdrawal amount, allowing you to initialize the exception with specific details. It also allows for an optional inner exception for wrapping other exceptions.

using System;

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

    public InsufficientFundsException(string message, decimal balance, decimal withdrawalAmount) : base(message)
    {
        Balance = balance;
        WithdrawalAmount = withdrawalAmount;
    }

    public InsufficientFundsException(string message, decimal balance, decimal withdrawalAmount, Exception innerException) : base(message, innerException)
    {
        Balance = balance;
        WithdrawalAmount = withdrawalAmount;
    }

    public override string ToString()
    {
        return $"{base.ToString()}\n  Balance: {Balance}\n  Withdrawal Amount: {WithdrawalAmount}";
    }
}

Throwing the Custom Exception

This code demonstrates how to throw the `InsufficientFundsException` when a withdrawal attempt exceeds the account balance. The `Withdraw` method checks if the withdrawal amount is greater than the balance, and if so, throws the custom exception, including the current balance and the attempted withdrawal amount. The `Main` method includes a try-catch block to handle the exception.

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("Withdrawal amount exceeds balance.", _balance, amount);
        }

        _balance -= amount;
    }

    public decimal GetBalance()
    {
        return _balance;
    }
}

public class Example
{
    public static void Main(string[] args)
    {
        BankAccount account = new BankAccount(100.00m);
        try
        {
            account.Withdraw(150.00m);
        }
        catch (InsufficientFundsException ex)
        {
            Console.WriteLine(ex);
            // Handle the exception appropriately (e.g., log it, inform the user)
        }
        catch (Exception ex)
        {
            Console.WriteLine("An unexpected error occurred: " + ex.Message);
        }
    }
}

Concepts Behind the Snippet

This snippet uses the principles of object-oriented programming, especially exception handling and custom exception creation. It allows for a more specific way to handle exceptions that can occur in certain situations, like withdrawing from a bank account. Exceptions are derived from a base `Exception` class. Custom exceptions allow the passing of contextual information for richer debugging and handling.

Real-Life Use Case

Imagine an e-commerce system. You could have a `ProductOutOfStockException` when a user tries to purchase a product that is no longer available, including information about the product ID and the requested quantity. This allows the system to handle the error gracefully, informing the user and potentially suggesting alternatives.

Best Practices

  • Specificity: Create exceptions that are as specific as possible to the error condition.
  • Context: Include relevant information in the exception to aid in debugging and handling.
  • Inner Exceptions: Use inner exceptions to wrap other exceptions, preserving the original error information.
  • Documentation: Document your custom exceptions clearly, explaining when they are thrown and what information they contain.
  • Avoid Swallowing Exceptions: Don't catch exceptions and do nothing with them. Log them or re-throw them (or a more appropriate exception) if you can't handle them.

Interview Tip

Be prepared to discuss the benefits of custom exceptions over using generic exceptions. Highlight the increased clarity and context they provide, and their contribution to more robust and maintainable code. Understand the exception hierarchy and how to best leverage existing exception types.

When to Use Them

Use custom exceptions when you need to handle errors that are specific to your application or domain. This allows you to write more targeted error handling logic and provide more informative error messages to users and developers. Avoid creating custom exceptions for common error scenarios that are already covered by standard .NET exceptions (e.g., `ArgumentNullException`, `InvalidOperationException`).

Memory Footprint

Creating custom exceptions doesn't inherently introduce a significant memory overhead. The memory footprint of an exception is primarily determined by the amount of information it stores (e.g., the message, stack trace, and any additional properties). Be mindful of the data you include in your custom exceptions, and avoid storing large or unnecessary objects.

Alternatives

While custom exceptions are powerful, consider these alternatives:

  • Result Objects: Return a result object that indicates success or failure and includes any relevant data or error messages. This can be useful for non-critical errors where an exception might be too heavy-handed.
  • Error Codes: Use predefined error codes to represent different error conditions. This can be more efficient than creating custom exceptions, but it may be less expressive.

Pros

  • Clarity: Custom exceptions make code easier to understand by explicitly defining the types of errors that can occur.
  • Context: They allow you to include relevant information about the error, making debugging and handling easier.
  • Maintainability: They improve maintainability by providing a clear and consistent way to handle errors.

Cons

  • Overhead: Creating and throwing exceptions can be more expensive than other error handling mechanisms (e.g., returning error codes).
  • Complexity: Defining and managing custom exceptions can add complexity to your codebase.
  • Misuse: Overusing custom exceptions can lead to cluttered and less readable code.

FAQ

  • When should I throw an exception instead of returning an error code?

    Throw exceptions for exceptional situations that the calling code cannot reasonably be expected to handle. Use error codes or result objects for more routine errors that the calling code should handle directly.
  • Can I catch multiple exception types in a single catch block?

    Yes, you can catch multiple exception types in a single catch block using exception filters (the `when` keyword in C#). You can also have multiple catch blocks to handle different exception types separately.