Click here to Skip to main content
15,880,651 members
Articles / Programming Languages / C#

The Rise of the Null-Object Pattern in C# 8

Rate me:
Please Sign up or sign in to vote.
3.74/5 (8 votes)
21 May 2020CPOL5 min read 9.9K   6   15
Null-object pattern in C# 8
In this post, we will explore through the land of null references, how to guard and work with them in C# 8.

As I probably mentioned in some of my older posts, I’m a big fan of code restrictions that can be imposed by the IDE. I think this is a reason why stylecop was/is so popular (I don’t know since I mostly use Rider nowadays and Resharper for Visual Studio).

Because of this, whenever I start a new project, I always turn on the feature to treat warnings as errors, just to get them out of the way from the start.

With the addition of the null reference in C# 8, I turn on this feature as well, so that if I need to use a null, it will be out of an actual thought out decision and not by default, this in combination with the “warnings as errors” option, doesn’t even let me compile without writing my code thoughtfully in regards to nulls.

What Is a Null Reference?

The link mentioned above goes into a lot more detail about it, but suffice to say that we have two new symbols to use in conjunction with reference types.

The Usage of ? at Declaration

In the past, only the value types such as custom structs or the base types like int, bool, double, etc. benefitted from having a default value and as such if we wanted a nullable variant, we needed to prefix the type definition with a ? which is some syntactic sugar sprinkled over the fact that when the code compiles, we would get a Nullable<T>, where T was of type struct, which would also give us access to the HasValue and Value properties to check if the variable is null.

In the case of references, there is no such background magic, but the compiler is smart enough now to know that in a non-nullable scenario (either by turning it on, for a section of code, a file or the whole project), that the reference variable (or field or property) cannot receive a null value and as such, it will highlight a warning or throw an error. This also means that because this is not a form of syntactic sugar to express something different like Nullable<T>, that we won’t have access to the HasValue and Value properties.

Since there is no point in a property for the value because the value is the variable itself, for those that was something akin to what Nullable<T> for reference variables declared as nullable, we could use an extension method, might not be as pretty but it does the job. Here’s an example of such an extension:

C#
public static class NullableObjectExtensions
{
    public static bool IsNull<T>(this T objectInstance) where T : new()
    {
        return objectInstance is null;
    }

    public static bool IsNotNull<T>(this T objectInstance) where T : new()
    {
        return !objectInstance.IsNull();
    }
}

I like to have both of them just so that I don’t need to look for the not operator, and it makes it easier to read.

The Usage of !for When We Know Something Isn’t Null

The ! operator is called the null-forgiving operator. So if we’re in a situation in which we know that something shouldn’t be null but we’re still getting errors, then we could postfix our variable with the a ! and the compiler will just ignore that check.

The need for this mostly comes from a shortcoming of the compiler, in that if we do have a nullable variable that is being checked by the IsNotNull extension method, even if the variable is not null, we will still get the warning/error, because the compiler doesn’t follow the check we have in a different method, so we have to tell the compiler “yes I know there’s a risk of a null reference, but I did just check it”.

One thing to note is that the compiler is smart enough to recognize if we checked for a null reference if it’s in the same method.

In the following code snippet, I will show when we will use the null-forgiving operator, when not, and another type of trick using pattern matching and the null-conditional operator.

C#
class Program
{
    static void Main(string[] args)
    {
        MyClassA a = new MyClassA();

        if (a.MyClassB.IsNotNull())     // checking for nullability with the extension method
        {
            a.MyClassB.Prop.ToString(); // this gives off a warning/error even if 
                                        // we checked for nullability
            a.MyClassB!.Prop.ToString();// this not gives off a warning/error 
                                        // because we used the null-forgiving operator
        }

        if (a.MyClassB != null)
        {
            a.MyClassB.Prop
                .ToString();            // this will not give off a warning/error 
                                        // because the compiler sees that we checked 
                                        // for nullability in this code block
        }

        if (!(a.MyClassB is null))
        {
            a.MyClassB.Prop
                .ToString();            // this won't throw an warning/error as well 
                                        // because the compiler knows that we're negating 
                                        // a pattern match for null
        }

        if (a.MyClassB is {})           // this is a somewhat better way to check if 
                                        // something not null, instead of pattern matching 
                                        // with null and then negating it
        {
            a.MyClassB.Prop.ToString(); // this also won't throw an warning/error
        }
    }
}

class MyClassA
{
    public MyClassB? MyClassB { get; set; }
}

class MyClassB
{
    public string Prop { get; set; } = string.Empty;
}

public static class NullableObjectExtensions
{
    public static bool IsNull<T>(this T objectInstance) where T : new()
    {
        return objectInstance is null;
    }

    public static bool IsNotNull<T>(this T objectInstance) where T : new()
    {
        return !objectInstance.IsNull();
    }
}

Now that we saw how we can use null reference, check for them with the help of the compiler, it’s time to return to the topic at hand, and that is to use an alternative to null checks by using the Null-Object pattern.

Moving to Null-Object Pattern

Likewise, I will not go into great detail as to what this pattern is because there are a lot of great resources about design patterns that explain them.

To use this, we need to create a static field that will give us a default object.

Here is an example of the modifications in place to use the Null object.

C#
class MyClassA
{
    public MyClassB MyClassB { get; set; } = MyClassB.None;
}

class MyClassB
{
    public static MyClassB None { get; } = new MyClassB();
    public string Prop { get; set; } = string.Empty;
}

By creating a static property that can only be read and with a default instance, we can start writing our code free of null checks, better yet since it’s a static, if we want to check if the field has been filled in with a proper instance, we just need to compare it to the None property since they will all use the same reference. So if we can have the following:

C#
MyClassA a = new MyClassA();
if (a.MyClassB == MyClassB.None)
{
  // Place code here.
}

Of course, there is a downside to this and that is since they are all using the same reference, if we do change something on the None instance, then the change will be accessible everywhere that reference is being used.

So a few solutions are as follows:

  • Write your classes with encapsulation and immutability in mind (easier said than done), that would encourage constructor parameters. Of course, by using this approach, then we are limited when using POCO classes for deserialization like for example from JSON, this could still work if deserializers can use reflection for setting private fields.
  • Do a hackish approach that is kinda ugly from my point of view but at least it works, and it’s better if it’s ugly rater than crash in production (time will tell if there is a better approach).

This is my hackish solution for now:

C#
void Main()
{
	MyClassA a = new MyClassA();
	Console.WriteLine(a.MyClassB.Prop);    // this will display the default text 
                                           // we inputted at creation time
	a.MyClassB.Prop = "456";               // this will throw an exception 
                                           // if we tried and changed it
}

class MyClassA
{
	public MyClassB MyClassB { get; set; } = MyClassB.None;
}

class MyClassB
{
	private string _propValue = string.Empty;

	public static MyClassB None { get; } = new MyClassB {
		Prop = "123"                       // example of default data for our null object
	};
	
	public string Prop
	{
		get { return _propValue; }
		set
		{
			if (this == None)
			{
				throw new InvalidOperationException($"Cannot set {nameof(Prop)} 
                      on instance of {nameof(None)}");
			}
			_propValue = value;
		}
	}
}

The obvious downside to this approach is that you need a check for each property that gets exposed to changes.

Conclusion

I hope you enjoyed this exploration through the land of null references, how to guard, and work with them.

I also hope that my hackish approach will find a better form, maybe one day when Rosylin will support AOP (we can all dream right? 😉).

See you next time, and happy coding.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Romania Romania
When asked, I always see myself as a .Net Developer because of my affinity for the Microsoft platform, though I do pride myself by constantly learning new languages, paradigms, methodologies, and topics. I try to learn as much as I can from a wide breadth of topics from automation to mobile platforms, from gaming technologies to application security.

If there is one thing I wish to impart, that that is this "Always respect your craft, your tests and your QA"

Comments and Discussions

 
QuestionSeveral mistakes Pin
wkempf5-Jun-20 3:09
wkempf5-Jun-20 3:09 
AnswerRe: Several mistakes Pin
Vlad Neculai Vizitiu5-Jun-20 3:41
Vlad Neculai Vizitiu5-Jun-20 3:41 
GeneralRe: Several mistakes Pin
wkempf5-Jun-20 4:04
wkempf5-Jun-20 4:04 
GeneralRe: Several mistakes Pin
Vlad Neculai Vizitiu5-Jun-20 4:25
Vlad Neculai Vizitiu5-Jun-20 4:25 
GeneralRe: Several mistakes Pin
wkempf5-Jun-20 4:39
wkempf5-Jun-20 4:39 
QuestionNull-object pattern not EFCore friendly Pin
Fernando A. Gomez F.22-May-20 8:12
Fernando A. Gomez F.22-May-20 8:12 
AnswerRe: Null-object pattern not EFCore friendly Pin
Vlad Neculai Vizitiu22-May-20 8:37
Vlad Neculai Vizitiu22-May-20 8:37 
QuestionNone? Pin
Paulo Zemek21-May-20 16:23
mvaPaulo Zemek21-May-20 16:23 
AnswerRe: None? Pin
Vlad Neculai Vizitiu21-May-20 21:56
Vlad Neculai Vizitiu21-May-20 21:56 
AnswerRe: None? Pin
Fernando A. Gomez F.22-May-20 8:19
Fernando A. Gomez F.22-May-20 8:19 
GeneralRe: None? Pin
Paulo Zemek22-May-20 18:27
mvaPaulo Zemek22-May-20 18:27 
An empty collection and a null collection are still different things, that we might discuss if being null or empty is better.

But for most types, there's no discussion... either you have it or not.
And, in my opinion, even the sample that checks to forbid None from being modified only shows that "None" was not a solution at all.

But I can see how it can be useful to avoid "null checks" when dealing with "no action" loggers or similar. Still not sure if it is worth at run-time, but I see the value of never checking for null and having a "default" that does nothing.

QuestionLanguage type selection forgotten Pin
Daan Acohen20-May-20 23:32
Daan Acohen20-May-20 23:32 
PraiseRe: Language type selection forgotten Pin
Vlad Neculai Vizitiu20-May-20 23:44
Vlad Neculai Vizitiu20-May-20 23:44 
SuggestionSmall detail Pin
phil.o20-May-20 16:31
professionalphil.o20-May-20 16:31 
PraiseRe: Small detail Pin
Vlad Neculai Vizitiu20-May-20 23:45
Vlad Neculai Vizitiu20-May-20 23:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.