You might often encounter null reference exceptions in your applications if null checks aren’t appropriately implemented. A null reference exception will abruptly terminate the execution of your program unless it is properly handled in your code. The null object pattern solves this problem, giving you an elegant way to handle null reference exceptions in your code.
This article discusses how we can implement the null object pattern in C# with relevant code examples wherever applicable. To work with the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.
Create a console application project in Visual Studio
First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2022 is installed in your system, follow the steps outlined below to create a new .NET Core console application project.
- Launch the Visual Studio IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window shown next, specify the name and location for the new project.
- Click Next.
- In the “Additional information” window, choose “.NET 7.0 (Standard Term Support)” as the Framework version you would like to use.
- Click Create.
We’ll use this .NET Core console application project to work with the null object design pattern in the subsequent sections of this article.
What is the null object pattern?
The null object pattern is a behavioral software design pattern that is used to encapsulate the handling of null values in an object-oriented programming language. It is used to help improve code readability and maintainability by eliminating the need for explicit null checks.
The null object pattern defines an abstract class that represents a null value. A concrete subclass is then created that inherits from the abstract class and provides concrete implementations for all of the methods. This concrete subclass is then used throughout the code base whenever a null value needs to be represented.
Why use the null object pattern?
There are a number of reasons to use the null object pattern when working with objects in C#. First, it can help to avoid null reference errors. Second, it can make code more readable by avoiding checks for null values. Third, it can improve performance by avoiding unnecessary calls to methods and properties. Finally, it can make it easier to unit test code.
Implementing the null object pattern in C#
To implement the null object pattern in C#, we’ll follow the three steps outlined below.
- Define an interface or an abstract class.
- Provide concrete implementations of the methods of the abstract class or the interface in a class that extends either of them.
- Define a null implementation by overriding the methods of the abstract base class or the interface and returning appropriate values for your null object.
Create an abstract class in C#
In the console project created earlier, create a class called AbstractProduct, delete the default generated code, and enter the following code.
public abstract class AbstractProduct
{
public abstract int Id { get; set; }
public abstract string Name { get; set; }
public abstract string GetProductDetails();
}
Implement methods of the abstract class in C#
Next, create another new class named Product in a file named Product.cs. Delete the default generated code and enter the following code instead.
public class Product : AbstractProduct
{
public override int Id
{
get;
set;
}
public override string Name
{
get;
set;
}
public override string GetProductDetails()
{
return $"Product Id: {Id}, Product Name: {Name}";
}
}
Implement a class to handle null values in C#
Create a class named NullProduct to handle null values, delete the default generated code, and enter the following code.
public class NullProduct : AbstractProduct
{
public override int Id
{
get;
set;
}
public override string Name
{
get;
set;
}
public override string GetProductDetails()
{
return $"Product Name: {Name}";
}
}
Complete our null object pattern example in C#
Finally, we’ll create the ProductRepository class used to work with the product data of our example application. ProductRepository contains a method named GetProduct that accepts the product ID as a parameter and returns either a product instance (if the product is found) or an instance of NullProduct if no product is found.
public class ProductRepository
{
List<Product> products = new List<Product>();
NullProduct? NotFound = new() { Id = -1, Name = "Not Available" };
public ProductRepository()
{
products.Add(
new Product()
{
Id = 1,
Name = "DELL Laptop"
});
products.Add(
new Product()
{
Id = 2,
Name = "Lenovo Laptop"
});
}
public AbstractProduct GetProduct(int id)
{
AbstractProduct product = products.Find(x => x.Id == id);
return product ?? NotFound;
}
}
Execute the application
Now, use the following piece of code to retrieve a product instance and call the GetProductDetails method to get the details about the product, if one is found.
ProductRepository productRepository = new ProductRepository();
var product = productRepository.GetProduct(1);
Console.WriteLine(product.GetProductDetails());
Figure 1 shows the output.
Note that, because the product ID (1 in this case) is available in the list of products, the product details (DELL Laptop) are displayed. Now let’s pass a product ID that doesn’t exist as the parameter to the GetProduct method, as shown in the code snippet given below.
ProductRepository productRepository = new ProductRepository();
var product = productRepository.GetProduct(3);
Console.WriteLine(product.GetProductDetails());
This time, the product will not be found and an appropriate message will be displayed at the console window as shown in Figure 2.
The null object design pattern helps you avoid potential null reference exceptions, simplifies your code, and decreases the amount of clutter that is associated with traditional methods for handling null values. By using a null object in lieu of a null value you can avoid runtime exceptions elegantly, and make your code easier to maintain in the long run.