Within this tutorial, you will learn about all the important nuances that you need to be aware of when using
null within .NET. Granted, this might sound like a trivial subject, however, I am confident that by the end of this article you will be surprised about the depth of capabilities .NET provides around
As the .NET framework has continued to evolve, the way in which you can use
null has also evolved. In this guide, you will get a practical history lesson through all these different changes and also look at how
null will be able to be used in the future as well. If this sounds good to you, read on 🔥🔥🔥
What can be null?
When it comes to thinking about checking for
null, the first step to consider is what we are checking
null against. At a high-level the obvious answer is data and within C# we have to deal with two categories of data, value types and reference types. What data you can assign
null and how you can check for
null will differ depending on which category of data you are working with 🤔
We do not have the time to do a deep dive between value and reference types here, however, a quick re-cap will be useful:
Value types: When you are dealing with data that is stored as a value type, that data will be directly assigned to that type. The value type will have its own memory allocation that contains a copy of the data. Value types include the following:
Reference types: The second category of data that you can work with are referred to as reference types. Reference types are more complicated animals compared to value types. Where a value type contains a single value, typically, a reference type will contain lots of data. As a reference type can contain lots of different types of data, the data can not be easily assigned directly to that type in memory. In order for the framework to store data assigned to a reference type, all the different bits of data are assigned areas in memory. A pointer/reference to these areas in memory are then assigned to the value of the reference type. Reference types include the following:
Nullable value types and Nullable reference types
One of the key considerations when checking for
null is thinking about the type of data that you are working with. By default, value types do not allow for
null to be assigned to them.
Nowadays it is obviously possible to use
null with value types and that is because of two features that were released as part of C# 2. Generics allowed for value types to be used as parameters to methods, classes, and interfaces. This feature allowed for the introduction of nullable value types. A nullable value type allows for
null to be assigned to a value type. This is possible via
Nullable<T>. To make a value type nullable you use the
When you use a
Nullable<T> you are essentially adding a wrapper around that value type. In the background, a value type still can not be assigned
null in memory, however, the wrapper allows for
null to be referenced, or a value to be assigned:
This code wraps a boolean value type and allows assigning a
null value to it. The nice thing about this nullable feature is that it allows you as an implementor the option of being more declarative with your code. You can clearly state at the source-code level when you expect something to be allowed to assign
null and when it should always have a value.
This intentional ability to state when something should be
null or not was a nice added capability to the framework. As reference types, like a
string, always allow for
null, this new capability did not extend to reference types. Assigning a reference type as null would cause a compiler warning, annoying 😡
This mismatch in capabilities changed in C# 8. Within C#8 nullable reference types were introduced.
In essence, a nullable reference type provides you with a flag to tell the compiler if you want to explicitly define if a reference type can allow for null or not. With this feature enabled, as of C# 8 it is now possible to use the nullable operator
? on a string without any compiler warnings:
As this feature changed the default framework behaviour and because Microsoft has great backwards compatibility when it was first released this feature was not enabled by default. Nullable reference types were released as an optional setting, however, this feature is now enabled by default from .NET 6 onwards.
You can either enable, or, disable this feature from within your
csproj file, or via the Visual Studio UI, To use the UI, select your project and open its
Properties menu option and search for the
The main takeaway is that from the latest version of .NET, you can now use nullable the same way regardless if it is a value or reference type. Now we know how we can assign
null to data, the next step is checking when a value contains
Checking for null
As .NET has evolved, the way in which you can check for
null has also changed. The classic way of checking for
null was to use the equal-to operator (
== ) with the
null keyword. This approach still works as expected and the code to check it will look like this:
Is: As part of C# v7, the
is operator was released. The
is operator can be used to check at run-time if an object is compatible with the given type or not. Using
is it is possible to also check for
null. This means we can refactor the code above like this:
In terms of good practice, it is recommended to use
is instead of
==. The main argument I have found for this recommendation is because
is ignores any object overloads that could be defined on the object that is being checked. If you take this code below (very contrite I know!),
== would result in
is would result in
Granted the likelihood of you ever having code like this in your project is tiny, however, it's best to stay safe!
Is and Not: As part of C# 9, you can use
is with logical
not. Before this update, if you wanted to do a negative check for
is you would have needed to write code like this:
not, your code can now be simplified like this:
Discards and Null-coalescing operator (??)
When you normally check for
null it is because you want to perform an additional action if something has not been set. Let us say want to implement a guard to ensure a class
instantiates with all the dependencies you expect it to have.
If the passed-in argument is
null, let us say you want an
ArgumentExcpetion to be thrown. Instead, of using an
if statement to check for
null, in a lot of situations your code can be simplified using the null-coalescing operator.
Released in C#8, the null coalescing operator is yet another addition to the framework to improve the way we can work with
null. The null coalescing operator is specified by using two question marks like this,
The operator can be added after any assignment that could result in a
null value. When defined it will return the left-hand operand if the value isn't
null, otherwise, it evaluates the right-hand operand if it is
null. This means you can simplify a lot of your
null checking code like this:
The null coalescing operator can also be combined with another feature introduced with another new C# 8 feature, discards. One limitation with the null coalescing operator is that you can not use it on a line of code on its own. The result of the coalescing process needs to be assigned to a type.
In our guard example, it is not possible to assign a type to some code that will throw an exception. This is where the use of the
discard comes into play. Discards are handy for tasks like pattern matching. The discard operation allows you to ignore anything that is assigned to it. When you combine a discard with a null-coalescing check, you can refactor our guard code to look like this:
Granted, this line can look a little funky at first glance, however, without the discard, you would run into a compiler error. Personally, I do not really use this type of code in production. Within your code, you will still need to use the classic
if statement in the majority of situations, so why use two different approaches in your codebase?
In general, I like to follow rules that I can use 100% of the time. I find following these types of rules makes the overall codebase easier to understand, however, do what makes you happy!
Combined, these two operators will give you a new way of structuring code, cool 😎
Parameter Null Checking In C# 11 and 12
That covers all the different ways you can work with
null within .NET up until this point in time (September 2022), however, as we have seen the framework is constantly improving. It is now time to look into the future!
C# 11 will be released at the end of 2022. Initially, within the early stages of the proposal, there was a new feature called parameter null checking that was being proposed. Unfortunately, parameter null checking didn't make the cut and it was removed from the C#11 release in June 2022. Hopefully, it will make it into C#12 but only time will tell.
The reason why I mention this unusable feature here is that if you search for
null and .NET online, you may bump into some articles that mention it. Instead of getting frustrated trying to implement something that doesn't work, armed with this knowledge about what it is and why it's not available might save you some time.
The idea behind the parameter null check is simple, to reduce the amount of boilerplate guard code that you need to use in an application. Regardless of which way you write your guard, you still need to write some code. With parameter null check, you can define within a method or constructor signature, which arguments can and can NOT be
null. The proposal is to allow a double-bang,
!! to be added ot the end of an argument, like this:
!! is applied to an argument, if the value passed in is
null then an exception will be thrown. I really like this idea and I hope it gets added back as it will reduce a lot of boilerplate code from needed from being written! Anything that makes me write code quick is all good in my eyes 🤩🤩
After researching this topic I was surprised at how many options we as developers have access too when working with
null in .NET. Looking back over its history you can see how many additional features and improvements have been made over the years. The good news is that within .NET 6 and upwards you have a very solid and robust way of checking for
null and you now know how ot make use of them all. Happy Coding 🤘