The Null object pattern in C#

Posted by : on

Category : C#

The Null reference problem

In C# as in most object oriented languages, a reference type might be null. This means that our reference doesn’t point to an object in memory. This creates a problem. Every time we want to use a variable that might point to an object, we have to make a null check. A null check is useful so that we can use methods or data of the object that the variable points to. If we don’t make that check and try to use any field or method of our object, our program will crash.

Having to make all those null checks in different parts of our program, takes time and makes our code harder to read.

The Null Object pattern solution

The Null Object pattern, tries to find a solution to this problem. While it may not be a solution that is helpful for every null reference problem, when being used in the right situation, can increase productivity and reduce bugs.

Sometimes someone may see the null object pattern called the default object pattern. The default object pattern is a superset of the null object, this means that the default object pattern is used in a way to always provide an object with default values. These values may have different meaning depending on our object and the situation that is being used. This holds true for the null object pattern also. The null object will have fields and behaviors that make sense, when our object isn’t initialized, which depend on the context that is being used.

In the following sections there are some implementations of the null object pattern, using the tools C# provides, but these solutions also help with the default object pattern.

The classic Null object

Let’s suppose that we have the following IEnemy interface and two implementations of enemies:

public interface IEnemy
{
    public string GetName();
    public int GetHP();
            
}

public class Goblin : IEnemy
{
    public int GetHP() => 20;
    public string GetName() => "Bob the green";
}

public class Skeleton : IEnemy
{
    public int GetHP() => 10;
    public string GetName() => "Bones the mighty";
}

A null object can have this implementation:

public sealed class NullEnemy : IEnemy
{
    public int GetHP() => 0;
    public string GetName() => "Anonymous";
}

The default values depend on our specific needs. Here we return as hit points the value zero and as a name the “Anonymous” string, but we could as easily return an empty string or any other value that makes sense for our program.

Now whenever we want to instantiate an enemy, we can get one from a factory class that we have created, or a null object if the enemy type doesn’t exist:

public class EnemyFactory
{
    public static IEnemy GetEnemy(string type)
    {
        if (type == "Goblin")
            return new Goblin();
        if(type == "Skeleton")
            return new Skeleton();

        return new NullEnemy();
    }
}

Our code now, that uses the IEnemy doesn’t need null checks, for example the following code

List<IEnemy> enemies = new List<IEnemy>() 
{   EnemyFactory.GetEnemy("Goblin"),
    EnemyFactory.GetEnemy(null),
    EnemyFactory.GetEnemy("Skeleton"),
    EnemyFactory.GetEnemy("Dragon")
};

foreach (var enemy in enemies)
    Console.WriteLine(enemy.GetName());

int totalEnemyHP = 0;
foreach (var enemy in enemies)
    totalEnemyHP += enemy.GetHP();

Console.WriteLine(totalEnemyHP);

will output

Bob the green
Anonymous
Bones the mighty
Anonymous
30

because the second and the fourth enemies are the null object.

But this implementation of the null object has a problem. Every time instead of returning null, we create a new instance of our object. This means that the code

if (enemies[0] == enemies[1])
    Console.WriteLine("The first and the second enemies are the same enemy");
else 
    Console.WriteLine("The first and the second enemies are different");

if (enemies[1] == enemies[3])
    Console.WriteLine("The second and the fourth enemies are the same enemy");
else
    Console.WriteLine("The second and the fourth enemies are different");

will have an output

The first and the second enemies are different
The second and the forth enemies are different

because of the referential equality, the second and the fourth enemies are considered different by the runtime. The solution is simple, our null object should be a singleton, that way, only one instance of it can ever exist

public class NullEnemySingleton : IEnemy
{
    private static readonly IEnemy instance = new NullEnemySingleton();
    private NullEnemySingleton() { }

    public static IEnemy Null => instance;

    public int GetHP() => 0;

    public string GetName() => "Anonymous";
}

public class EnemyFactory
{
    public static IEnemy GetEnemy(string type)
    {
        if (type == "Goblin")
            return new Goblin();
        if(type == "Skeleton")
            return new Skeleton();

        return NullEnemySingleton.Null;
    }
}

now the above code will have this output:

The first and the second enemies are different
The second and the fourth enemies are the same enemy

and we have an easy way, to check if an instance of our IEnemy, is the null object

if (enemies[1] == NullEnemySingleton.Null) 

Implementing the Null object with the help of default Interface methods

By using the default interface implementation of C# and the static modifier that is allowed in interfaces from C# 11, we can give default behaviour to our methods. With that feature of C#, we now have another option.

The problem with the null object pattern, is that the consumer may not know, that we have a null object and for that reason performs the null checks anyway. By using the default interface implementation we can now inform anyone that has access to our interface, that there is a default/Null object that can be used.

We can achieve that, by creating a private class inside our interface and then return it:

public interface IEnemy
{
    public string GetName();
    public int GetHP();

    public static IEnemy Null => DefaultEnemy.Null;

    private class DefaultEnemy : IEnemy
    {
        private static readonly IEnemy instance = new DefaultEnemy();
        private DefaultEnemy() { }

        public static IEnemy Null => instance;
        public int GetHP() => 0;
        public string GetName() => "Anonymous";
    }
}

now our factory looks like this:

public class EnemyFactory
{
    public static IEnemy GetEnemy(string type)
    {
        if (type == "Goblin")
            return new Goblin();
        if (type == "Skeleton")
            return new Skeleton();

        return IEnemy.Null;
    }
}

Our previous code has the same results and the null object check, is more intuitive now, because it is written like this:

if (enemies[1] == IEnemy.Null) 

And this is it, about the null object pattern. I hope you enjoyed this article and as always if you have any questions or comments you can use the comments section or contact me directly via the contact form or email, also if you don’t want to miss any of the new articles, you can always subscribe to my newsletter or the RSS feed.


Follow me: