Null Object Pattern in C#

A Null object is an object that encapsulates the absence of an object. It provides the do nothing behavior and returns the defaults. The Null object is used whenever object reference would have been null. The use of Null Object pattern simplifies the client code and makes it less error prone.

Consider a simple sales application, which verifies the customer account and check whether customer does have the gold membership. Gold members get varied benefits like discount, free shipping etc.

Below is the code snippet of the main method that gets the account details of the customers and retrieves their gold membership profile and applies all the benefits applicable to the gold members.

string userName = args[0];
string pin = args[1];
Order order = new Order();

for (int i = 2; i < args.Length; i++)
{
    order.Amount += Convert.ToDecimal(args[i]);
}

var account = AccountController.GetAccount(userName, pin);

if (account == null)
{
    Console.WriteLine("Invalid Account");
}
else
{
    var gProfile = GoldMembershipController.GetProfile(account);

    if (gProfile != null)
    {
        order.Amount -= gProfile.GetDiscount(order.Amount);
    }

    if (gProfile != null)
    {
        if (!gProfile.IsShippingFree)
        {
            order.Amount += order.GetShippingCharges();
        }
    }
    else
    {
        order.Amount += order.GetShippingCharges();
    }

    Console.WriteLine(string.Format("Total Amount: {0}", order.Amount));
}

If customer has the valid account then AccountController.GetAccount(userName, pin) returns the Account object else it returns the null. Similarly if customer has gold membership then GoldMembershipController.GetProfile(account) returns the GoldMembership object and in case user doesn’t have gold membership, it returns the null.

As you can see in the above code snippet, there are lots of repetitive conditional statements that check the null reference. Conditional statements are actually written to follow different paths based on the result of the condition. In the above gold membership case we have the same flow and conditional statements are written only to check the null references. This doesn’t only make the code longer and hard to understand but also very error prone as it is very easy to miss a null guard in a large program. This is the problem that Null Object pattern is trying to solve.

After implementing the Null Object pattern GoldMembershipController.GetProfile(accountId) will always return an object. In case customer doesn’t have gold membership, it will return a Null Object. To implement the Null Object pattern, we will create an IGoldMembership interface. Both GoldMembership and NullGoldMembership class will implement this interface. NullGoldMembership class is a neutral class that does nothing and simply returns the defaults. We will also modify the GoldMembershipController.GetProfile(accountId) method. It will return the GoldMembership object when user has the gold membership and NullGoldMembership when user doesn’t have the gold membership.

public interface IGoldMembership
    {
        decimal GetDiscount(decimal amount);
        bool IsShippingFree { get; set; }
    }

public class GoldMembership : IGoldMembership
    {
        public GoldMembership(string userName)
        {
            // get gold member profile of this user

            IsShippingFree = true;
        }

        public bool IsShippingFree { get; set; }

        public decimal GetDiscount(decimal amount)
        {
            decimal discount = 100;

            //calculate discount

            return discount;
        }
    }

public class NullGoldMembership : IGoldMembership
    {
        public NullGoldMembership()
        {
            IsShippingFree = false;
        }

        public decimal GetDiscount(decimal amount)
        {
            return 0;
        }

        public bool IsShippingFree { get; set; }
    }

public class GoldMembershipController
    {
        public static IGoldMembership GetProfile(Account account)
        {
            // if this account has gold membership
            // return profile else return null

            if (account.Type == "GoldMember")
            {
                return new GoldMembership(account.UserName);
            }
            else
            {
                return new NullGoldMembership();
            }
        }
    }

After implementing the Null Object pattern our code of the main method will look like below:

            string userName = args[0];
            string pin = args[1];
            Order order = new Order();

            for (int i = 2; i < args.Length; i++)
            {
                order.Amount += Convert.ToDecimal(args[i]);
            }

            var account = AccountController.GetAccount(userName, pin);

            if (account == null)
            {
                Console.WriteLine("Invalid Account");
            }
            else
            {
                var gProfile = GoldMembershipController.GetProfile(account);

                order.Amount -= gProfile.GetDiscount(order.Amount);

                if (!gProfile.IsShippingFree)
                {
                    order.Amount += order.GetShippingCharges();
                }

                Console.WriteLine(string.Format("Total Amount: {0}", order.Amount));
            }

After implementing the Null Object pattern, our client side code looks much simpler now. It is not only concise but also easy to understand.

However Null Object pattern doesn’t completely eliminate the use of null reference. We may still have to follow different paths based on whether object is present or absent. In those cases we will still continue to use null reference. For example, if user doesn’t have a valid account we are not going to process the transaction. In that case we will simply show the error message. In those scenarios and we still continue to use and check null references like below:

            var account = AccountController.GetAccount(userName, pin);

            if (account == null)
            {
                Console.WriteLine("Invalid Account");
            }
            else
            {
                // process the transaction
            }

Happy coding!

Sajad Deyargaroo

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read