Learn how to implement custom exceptions in C# and why they're useful.
An exception is a runtime error in a program that violates a system or application constraint, or a condition that is not expected to occur during normal execution of the program. Possible causes of exceptions include attempting to connect to a database that no longer exists, when a program tries to divide a number by zero, or opening a corrupted XML file. When these occur, the system catches the error and raises an exception.
In .NET, an exception is represented by an object instance with properties to indicate where in the code the exception was encountered and a brief description of what caused the exception. We have different exception classes representing different types of errors and they all inherit from the System.Exception
base class. The SystemException
class inherits from the Exception
base class. The OutOfMemoryException
, StackOverflowException
, and ArgumentException
classes inherit from SystemException
. The ArgumentException
class has two other classes which derive from it: the ArgumentNullException
and ArgumentOutOfRangeException
classes. The ArithmeticException
class derives from the Exception
base class. The OverflowException
and DivideByZero
exceptions then inherit from the ArithmeticException
class. We also have the ApplicationException
class, which is derived from Exception
base class.
Additionally, we can define our own exception classes and they can derive from the Exception
base class. The exceptions we define in our projects are called user-defined or custom exceptions. A use case for creating our own exception class is when you’re interfacing with some external service that returns error codes to indicate errors. You can then have a way to translate the error codes into custom exceptions using something like the Gateway or Facade design patterns.
When creating custom exception classes, they should inherit from the System.Exception
class (or any of your other custom exception classes from the previous section). The class name should end with the word Exception, and it should implement at least the three common constructors of exception types.
Let’s look at an example application that should raise an exception when an account balance is less than the transaction amount. Create a new console application project. Add a file InsufficientFuncException.cs with the following class definition:
[System.Serializable]
public class InsufficientFuncException : System.Exception
{
private static readonly string DefaultMessage = "Account balance is insufficient for the transaction.";
public string AccountName { get; set; }
public int AccountBalance { get; set; }
public int TransactionAmount { get; set; }
public InsufficientFuncException() : base(DefaultMessage) { }
public InsufficientFuncException(string message) : base(message) { }
public InsufficientFuncException(string message, System.Exception innerException)
: base(message, innerException) { }
public InsufficientFuncException(string accountName, int accountBalance, int transactionAmount)
: base(DefaultMessage)
{
AccountName = accountName;
AccountBalance = accountBalance;
TransactionAmount = transactionAmount;
}
public InsufficientFuncException(string accountName, int accountBalance, int transactionAmount, System.Exception innerException)
: base(DefaultMessage, innerException)
{
AccountName = accountName;
AccountBalance = accountBalance;
TransactionAmount = transactionAmount;
}
protected InsufficientFuncException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
We defined an exception class named InsufficientFuncException
which derives from the System.Exception
base class. It contains the properties TransactionAmount
, AccountBalance
and AccountName
, which will help provide more info about the error. We also have a default message variable which will be set as the Message
property when no message argument is supplied from the constructor. The first three public constructors are the three standard constructors for exception types. The other constructors accept arguments accountName
to indicate the owner of the account, accountBalance
to indicate the current account balance, and transactionAmount
so we know how much was requested for the transaction. We also marked the class as serializable so it can be used across app domains.
Custom exceptions are thrown and caught the same way as built-in exception types in .NET. To use the custom exception we defined, add a new file Account.cs with the following content:
class Account
{
public Account(string name, int balance)
{
Name = name;
Balance = balance;
}
public string Name { get; private set; }
public int Balance { get; private set; }
public void Debit(int amount)
{
if (Balance < amount) throw new InsufficientFuncException(Name, Balance, amount);
Balance = Balance - amount;
}
public void Credit(int amount) => Balance = amount + Balance;
}
This class holds the account details with methods to add and subtract from the balance. The InsufficientFuncException
exception is thrown when the Debit()
method is called with a transaction amount lower than the account balance.
We will now use this class and perform a debit transaction and see this exception class being utilized. Update Program.cs with the code below.
using System;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World Bank!");
var account = new Account("James Beach", 150);
try
{
account.Debit(200);
}
catch (InsufficientFuncException ex)
{
Console.WriteLine("Encountered exception \nException Message: " + ex.Message);
Console.WriteLine("Account Balance: " + ex.AccountBalance);
Console.WriteLine("Transaction Amount: " + ex.TransactionAmount);
}
Console.Read();
}
}
}
The code above creates an Account
object with a balance of 150
. Then it calls the Debit()
method with an amount of 200
, which is higher than the balance. This should throw an exception, and we’ll log that information to the console. When you run the program, you should get the following in the console.
Hello World Bank!
Encountered exception
Exception Message: Account balance is insufficient for the transaction.
Account Balance: 150
Transaction Amount: 200
You should notice that it catches the exception, and the properties of the exception type we defined makes it easy to tell which account had this error, the account balance, and the requested transaction amount.
Custom exceptions are exception types you define in your project. They’re useful when the built-in exception types don’t meet your needs. For example, if you’re building a library or framework, and you want consumers of that library to react to exceptions from the library differently than they would for built-in exception types. These user-defined exception classes should inherit from the System.Exception
base class and implement the three common constructors found in exception types.
Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.