Pluralsight Logo
Author avatar

Peter Mbanugo

Author badge Author

Understanding, Defining, and Using Custom Exceptions

Peter Mbanugo

Author BadgeAuthor
  • Nov 26, 2018
  • 9 Min read
  • 12 Views
  • Nov 26, 2018
  • 9 Min read
  • 12 Views
C#

Introduction

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.

What Are Exceptions?

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.

Understanding and Defining Custom Exceptions

.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[System.Serializable]
public class AccountBlockedException : System.Exception
{
    private static readonly string DefaultMessage = "The transaction cannot be performed on a blocked account";

    public string AccountName { get; set; }
    public string TransactionType { get; set; }

    public AccountBlockedException() : base(DefaultMessage) { }
    public AccountBlockedException(string message) : base(message) { }
    public AccountBlockedException(string message, System.Exception innerException)
    : base(message, innerException) { }

    public AccountBlockedException(string accountName, string transactionType)
    : base(DefaultMessage) {
        AccountName = accountName;
        TransactionType = transactionType;
    }

    public AccountBlockedException(string accountName, string transactionType, System.Exception innerException)
    : base(DefaultMessage, innerException) {
        AccountName = accountName;
        TransactionType = transactionType;
    }

    public AccountBlockedException(string accountName, string transactionType, string message)
    : base(message) {
        AccountName = accountName;
        TransactionType = transactionType;
    }

    public AccountBlockedException(string accountName, string transactionType, string message, System.Exception innerException)
    : base(message, innerException) {
        AccountName = accountName;
        TransactionType = transactionType;
    }

    protected AccountBlockedException(
        System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) : base(info, context) { }

}
csharp

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.

Using Custom Exception

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 bool IsBlocked { get; set; }

    public void Credit(int amount)
    {
        if (IsBlocked)
        {
            throw new AccountBlockedException(Name, "Credit");
        }
        Balance = amount + Balance;
    }

    public void Debit(int amount)
    {
        if (IsBlocked)
        {
            throw new AccountBlockedException(Name, "Debit");
        }
        Balance = Balance - amount;
    }
}
csharp

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var accountToDebit = new Account("Francis John", 900);
            var accountToCredit = new Account("Francisca Grey", 900);
            BlockAccount(accountToCredit);
            TransferFund(200, accountToDebit, accountToCredit);
        }
        catch (AccountBlockedException ex)
        {
            Console.WriteLine("Oh no! Something went wrong");
            Console.WriteLine(ex.Message);
            Console.WriteLine("Account name: {0}", ex.AccountName);
            Console.WriteLine("Transaction type: {0}", ex.TransactionType);
        }
    }

    static void BlockAccount(Account account)
    {
        account.IsBlocked = true;
    }

    static void TransferFund(int amount, Account accountToDebit, Account accountToCredit)
    {
        Console.WriteLine("Initiating transaction");
        Console.WriteLine("Will debit account {0} for the sum of {1}... \n", accountToDebit.Name, accountToDebit.Balance);
        accountToDebit.Debit(amount);
        Console.WriteLine("Will credit account {0} for the sum of {1}... \n", accountToDebit.Name, accountToDebit.Balance);
        accountToCredit.Credit(amount);
        Console.WriteLine("Transfer fund transaction completed");
    }
}
csharp

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.

1
2
3
4
5
6
7
8
9
Initiating transaction
Will debit account Francis John for the sum of 900...

Will credit account Francis John for the sum of 700...

Oh no! Something went wrong
The transaction cannot be performed on a blocked account
Account name: Francisca Grey
Transaction 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.

Wrap Up

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!

1