C# tutorials > Core C# Fundamentals > Exception Handling > How do you create and use custom exception types?

How do you create and use custom exception types?

Creating custom exception types in C# allows you to handle specific errors that are unique to your application or domain. This provides more clarity and control over error handling, making your code more robust and maintainable. This tutorial will walk you through the process of defining, throwing, and catching custom exceptions.

Defining a Custom Exception Class

To create a custom exception, you need to create a new class that inherits from the System.Exception class or one of its subclasses (e.g., System.ApplicationException or System.SystemException). It's generally recommended to inherit directly from System.Exception. This class should include constructors to handle different scenarios, such as a default message, an inner exception, or custom data relevant to the exception.

In the example above:

  • InsufficientFundsException is a custom exception class derived from Exception.
  • It includes properties like Balance and WithdrawalAmount to store relevant information about the error.
  • It has multiple constructors to handle different ways of initializing the exception, including passing an inner exception for wrapping other exceptions.
  • It overrides the ToString() method for a more informative representation of the exception.

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, Exception innerException, decimal balance, decimal withdrawalAmount) : base(message, innerException)
    {
        Balance = balance;
        WithdrawalAmount = withdrawalAmount;
    }

    public override string ToString()
    {
        return $"{Message}\nBalance: {Balance}\nWithdrawal Amount: {WithdrawalAmount}";
    }
}

Throwing a Custom Exception

To raise or trigger a custom exception, use the throw keyword followed by an instance of your custom exception class. Include a meaningful message and any relevant data in the exception's constructor. This signals that an error has occurred and transfers control to the nearest exception handler.

In the example above:

  • The Withdraw method of the BankAccount class checks if the withdrawal amount exceeds the balance.
  • If it does, it throws a new InsufficientFundsException with a relevant message and the current balance and withdrawal amount.

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 for withdrawal.", _balance, amount);
        }
        _balance -= amount;
    }

    public decimal GetBalance()
    {
        return _balance;
    }
}

Catching a Custom Exception

To handle a custom exception, use a try-catch block, specifically catching your custom exception type. This allows you to execute specific error handling logic based on the type of exception that occurred. It's good practice to also include a general catch block (catch (Exception ex)) to handle any unexpected exceptions.

In the example above:

  • The code attempts to withdraw an amount that exceeds the balance.
  • The try block encloses the code that might throw an exception.
  • The catch (InsufficientFundsException ex) block specifically catches the InsufficientFundsException and handles it by printing the exception's details to the console.
  • The catch (Exception ex) block catches any other exceptions that might occur.
  • The finally block ensures that the message "Withdrawal attempt completed." is always printed, regardless of whether an exception was thrown or caught.

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

        try
        {
            account.Withdraw(150);
        }
        catch (InsufficientFundsException ex)
        {
            Console.WriteLine(ex.ToString());
            // Handle the exception appropriately, e.g., log the error, display a message to the user.
        }
        catch (Exception ex)
        {
            Console.WriteLine("An unexpected error occurred: " + ex.Message);
            // Handle other exceptions.
        }
        finally
        {
            Console.WriteLine("Withdrawal attempt completed.");
        }
    }
}

Concepts Behind the Snippet

The core concepts behind custom exceptions are:

  • Inheritance: Custom exceptions inherit from the base Exception class, allowing them to be treated as general exceptions while retaining specific information.
  • Encapsulation: Custom exceptions encapsulate relevant data about the error, making it easier to handle and debug.
  • Clarity: They provide clear indications of specific error conditions in your application.
  • Specific Handling: They enable targeted error handling logic.

Real-Life Use Case Section

Consider an e-commerce application. A custom exception like ProductOutOfStockException could be used when a user tries to purchase a product that is no longer available. This allows the application to handle this specific error by displaying an appropriate message to the user and preventing the order from being processed.

Another Example: A InvalidEmailFormatException when validating user input.

Best Practices

  • Provide Meaningful Messages: Exception messages should clearly describe the error that occurred.
  • Include Relevant Data: Include properties that store relevant data about the error.
  • Use Inner Exceptions: When catching and re-throwing exceptions, preserve the original exception by using inner exceptions.
  • Document Your Exceptions: Document the purpose and usage of your custom exceptions.
  • Don't Overuse Exceptions: Use exceptions for exceptional cases, not for normal program flow.

Interview Tip

When discussing custom exceptions in an interview, be prepared to explain why they are useful, how to create them, and how they contribute to more robust and maintainable code. You should be able to describe scenarios where custom exceptions are beneficial and explain how to handle them properly.

When to Use Them

Use custom exceptions when:

  • You need to handle specific error conditions that are unique to your application.
  • You want to provide more detailed information about errors.
  • You need to differentiate between different types of errors for specific handling logic.

Memory Footprint

Custom exceptions, like all objects in C#, consume memory. The memory footprint depends on the properties you include in your custom exception class. Avoid adding unnecessary data to the exception to minimize its memory usage. Keep in mind that frequent throwing and catching of exceptions can impact performance due to the overhead of stack unwinding and exception handling.

Alternatives

Alternatives to using custom exceptions include:

  • Return Codes: Returning error codes from methods to indicate success or failure. However, this approach can be less readable and harder to maintain.
  • Event Logging: Logging error events without throwing exceptions. This approach is suitable when the error is non-critical and doesn't require immediate handling.
  • Using Existing Exceptions: Sometimes you can leverage built-in .NET exceptions. For example, ArgumentException or its derivatives (ArgumentNullException, ArgumentOutOfRangeException) might be sufficient.

Pros

  • Clarity: Improve code readability and maintainability.
  • Specific Handling: Enable targeted error handling.
  • Encapsulation: Encapsulate relevant error data.
  • Robustness: Increase the robustness of your application.

Cons

  • Overhead: Can introduce performance overhead if overused.
  • Complexity: Can add complexity to the codebase if not used judiciously.
  • Maintenance: Requires careful maintenance to keep exception types and messages up-to-date.

FAQ

  • When should I use a custom exception instead of a built-in exception?

    Use a custom exception when the error condition is specific to your application's domain and cannot be adequately represented by a built-in exception. This allows you to provide more context and handle the error more effectively.
  • Should my custom exception inherit from ApplicationException or Exception?

    Inherit directly from System.Exception. ApplicationException was intended to be the base class for application-specific exceptions, but this pattern is no longer recommended. Inheriting from Exception provides sufficient functionality and avoids potential confusion.
  • How can I include additional information in my custom exception?

    Add properties to your custom exception class to store relevant data about the error. Populate these properties in the exception's constructor and use them in your error handling logic.