Writing software is a complex undertaking and it is quite common for even the best software to ship with various problems. Sometimes the problem is caused by bad code, other times a problem is caused by bad user input that has not been accounted for in the application’s code. Regardless of the cause of the problem, the end result is that the application does not work as expected. At this point, we say the application has encountered an error. In .NET, exceptions are thrown when a running application encounters an error.
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 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.
.NET provides built-in exception classes that we can throw when the business rule of our application gets violated. However, if none of these built-in exception types meet our needs, we can create our own exception classes. The exception classes we define in our projects are what is referred to as custom exceptions. You can define and use custom exceptions 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 do for built-in exception types. Another useful scenario is if 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 implementing custom exception classes, they should inherit from the System.Exception
class (or any of your other custom exception classes). 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 be able to transfer funds between two accounts. The business rule is that you can't credit or debit an account that is blocked, and this is where we will throw the custom exception. Create a new console application project. Add a file AccountBlockedException.cs with the following class definition:
1[System.Serializable]
2public class AccountBlockedException : System.Exception
3{
4 private static readonly string DefaultMessage = "The transaction cannot be performed on a blocked account";
5
6 public string AccountName { get; set; }
7 public string TransactionType { get; set; }
8
9 public AccountBlockedException() : base(DefaultMessage) { }
10 public AccountBlockedException(string message) : base(message) { }
11 public AccountBlockedException(string message, System.Exception innerException)
12 : base(message, innerException) { }
13
14 public AccountBlockedException(string accountName, string transactionType)
15 : base(DefaultMessage) {
16 AccountName = accountName;
17 TransactionType = transactionType;
18 }
19
20 public AccountBlockedException(string accountName, string transactionType, System.Exception innerException)
21 : base(DefaultMessage, innerException) {
22 AccountName = accountName;
23 TransactionType = transactionType;
24 }
25
26 public AccountBlockedException(string accountName, string transactionType, string message)
27 : base(message) {
28 AccountName = accountName;
29 TransactionType = transactionType;
30 }
31
32 public AccountBlockedException(string accountName, string transactionType, string message, System.Exception innerException)
33 : base(message, innerException) {
34 AccountName = accountName;
35 TransactionType = transactionType;
36 }
37
38 protected AccountBlockedException(
39 System.Runtime.Serialization.SerializationInfo info,
40 System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
41
42}
We defined a custom exception named AccountBlockedException
which derives from the System.Exception
base class. It contains the properties TransactionType
and AccountName
, and 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 and transactionType
to specify if it was a credit or debit transaction that caused the error.
Custom exceptions are thrown and caught the same way as pre-defined exception types in .NET. To use the custom exception we defined, add a new file Account.cs to the project. Copy and paste the code below into it.
1class Account
2{
3 public Account(string name, int balance)
4 {
5 Name = name;
6 Balance = balance;
7 }
8 public string Name { get; private set; }
9 public int Balance { get; private set; }
10 public bool IsBlocked { get; set; }
11
12 public void Credit(int amount)
13 {
14 if (IsBlocked)
15 {
16 throw new AccountBlockedException(Name, "Credit");
17 }
18 Balance = amount + Balance;
19 }
20
21 public void Debit(int amount)
22 {
23 if (IsBlocked)
24 {
25 throw new AccountBlockedException(Name, "Debit");
26 }
27 Balance = Balance - amount;
28 }
29}
The Account
class is where we throw the AccountBlockedException
custom exception. The IsBlocked
property indicates if an account is blocked or not. The methods Credit
and Debit
check if an account is blocked before performing the transaction. If it's blocked, it throws the AccountBlockedException
exception with a constructor to specify the account name and transaction type.
Open Program.cs file and update it with the following code:
1class Program
2{
3 static void Main(string[] args)
4 {
5 try
6 {
7 var accountToDebit = new Account("Francis John", 900);
8 var accountToCredit = new Account("Francisca Grey", 900);
9 BlockAccount(accountToCredit);
10 TransferFund(200, accountToDebit, accountToCredit);
11 }
12 catch (AccountBlockedException ex)
13 {
14 Console.WriteLine("Oh no! Something went wrong");
15 Console.WriteLine(ex.Message);
16 Console.WriteLine("Account name: {0}", ex.AccountName);
17 Console.WriteLine("Transaction type: {0}", ex.TransactionType);
18 }
19 }
20
21 static void BlockAccount(Account account)
22 {
23 account.IsBlocked = true;
24 }
25
26 static void TransferFund(int amount, Account accountToDebit, Account accountToCredit)
27 {
28 Console.WriteLine("Initiating transaction");
29 Console.WriteLine("Will debit account {0} for the sum of {1}... \n", accountToDebit.Name, accountToDebit.Balance);
30 accountToDebit.Debit(amount);
31 Console.WriteLine("Will credit account {0} for the sum of {1}... \n", accountToDebit.Name, accountToDebit.Balance);
32 accountToCredit.Credit(amount);
33 Console.WriteLine("Transfer fund transaction completed");
34 }
35}
In this file, we declared a method BlockAccount
to block an account and the TransferFund
method to move money between two different accounts. In the Main
method we created two account objects, blocked one of them by calling BlockAccount(accountToCredit);
and called the TransferFund
method to transfer funds. This code should generate an exception (which will be caught by the catch block) when you run it. Running the application should give us the following output in the console.
1Initiating transaction
2Will debit account Francis John for the sum of 900...
3
4Will credit account Francis John for the sum of 700...
5
6Oh no! Something went wrong
7The transaction cannot be performed on a blocked account
8Account name: Francisca Grey
9Transaction type: Credit
This throws an exception because the account with the name Francisca Grey
is blocked. The AccountName
and TransactionType
properties have proven to be useful in telling which account is blocked and the type of transaction that caused the error.
Custom exceptions are exception types you define yourself in your project. They basically inherit from the Exception
base class and implement the three common constructors found in exception types. They can be used in scenarios where you're building a library and you want the users of that library to properly handle exceptions raised from the library. I showed you an example that took you through defining a custom exception and using. This should leave you equipped to properly define and use custom exception types!