Nullable reference types is the default behavior of reference types in C# 7.3 and below versions. Well, what do I mean by that? When we declare reference types, pre-C# 8, it’s always nullable, meaning it can be assigned a null value. Hence we need to check it against null to avoid NullReferenceException
.
The newly introduced feature, that was released as a part of C# 8 language enhancement is a non-nullable reference type, along with the existing nullable reference type behavior.
In the previous blog, where I covered the top 5 C# 8 features in my opinion, I did intentionally not cover this topic. Since I was not sure how useful it is going to be before experimenting with it. Plus I was not sure of its usage. After trying it out, now I am confident to write about it.
Three topics I am going to cover in this post
- What are nullable and non-nullable reference types?
- How to configure a C# project to use this feature?
- A code example of the Nullable reference types.
A bonus point I will cover towards the end of the article, is a refresher to nullable value types. This will allow us to compare/contrast some of the syntax and features that are available on one and not on the other.
What are nullable and non-nullable reference types?
Nullable reference types
When the variables of a reference type can be null, the reference type is considered nullable reference type. That’s the standard behavior of all reference types in C# 7.3 and below. E.g.,
MyType obj = null;
In the above example, I am assigning the value null
to a variable obj
of type MyType
. As I mentioned earlier, this is a very common scenario we are used to. And will probably be using going forward as well for most cases.
The pitfall of this implementation is that every time we use this instance, we have to make sure we check for null. Otherwise, we have the potential of getting a NullReferenceException
when using it, if the instance was not initialized to a proper reference. The C# compiler will never throw an error or warning which suggests us that the instance was never initialized.
Non-nullable reference types
The non-nullable reference type is a concept introduced as a part of C# 8. The idea here is that we can declare a reference type as non-nullable. Meaning, it cannot be initialized to null. And the members of the reference type also cannot be null.
Once we configure a C# Project or a class to be non-nullable (I will show how to do that later), if we set the instance of the class or its variable as null, we will get compiler warning. The compiler does not result in any error. Which means we can still go ahead and run the project without any issue. The warning is to show the intent of the design more than anything else. This definitely helps since we will not get NullReferenceException
anywhere in the application.
How to configure a C# project to use this feature?
There are two ways we can configure our project to use this new feature, where we can say every reference type is non-nullable. We can either do it through project settings or through page directive.
Firstly, for doing it through project settings, in Visual Studio 2019, we can click on the project, and it will open up in the editor. Once the file is opened, we can add the below tag in the file and save it.
<nullable>enable</nullable>
NOTE: Make sure that this tag is inside of a PropertyGroup tag.
Secondly, for doing it through a directive, we can use a directive #nullable enable
in a class file. This will ensure any reference type declared inside of this file are non-nullable.
This is a way to express a design decision, which was not available pre-C# 8. This makes the intent of the design more clear for sure.
A code example of the Nullable reference types
Firstly, in the code example, I will create a class NonNullableType
with a single string property NonNullableProperty
. The property will have a default value of a string, which is null.
Secondly, I will set the instance of the class NonNullableType
as null
in the TestMethod
method of the TestClass
class.
Since I did not add a #nullable
directive or updated the project file to make it nullable, when I compile the project I will not get any warning or error. This is standard behavior with any pre-C# 8 version.
using System;
namespace NonNullableTypeExample
{
public class NonNullableType
{
public string NonNullableProperty { get; set; }
}
public class TestClass
{
public void TestMethod()
{
NonNullableType nonNullableType = null;
Console.WriteLine(nonNullableType.NonNullableProperty);
}
}
}
Finally, when I compile the solution, there is no error or warning, as expected.
Using #nullable
Now, I will update the project to add the #nullable
directive with a value of enable
in the file where I have declared the NonNullableType
class. I will declare the #nullable
directive above the namespace declaration. This will enable the nullable reference type feature for the class. Once I add the directive to the file and recompile the solution, I will receive three warnings.
- Firstly, a warning is for the property
NonNullableProperty
, since this cannot have a default value ofnull
. The warning message :warning CS8618: Non-nullable property 'NonNullableProperty' is uninitialized. Consider declaring the property as nullable.
- Secondly, a warning for the instance of
NonNullableType
, since this cannot be assigned anull
value. The warning message:warning CS8600: Converting null literal or possible null value to non-nullable type.
- Thirdly, when we use the instance of
NonNullableType
to access its property since it is trying to dereference an instance which is null. The warning message:warning CS8602: Dereference of a possibly null reference.
using System;
#nullable enable
namespace NonNullableTypeExample
{
public class NonNullableType
{
public string NonNullableProperty { get; set; }
}
public class TestClass
{
public void TestMethod()
{
NonNullableType nonNullableType = null;
Console.WriteLine(nonNullableType.NonNullableProperty);
}
}
}
If we want to avoid the warning, all we have to do is to declare the instance with ?
Syntex, the way we do for nullable value types.
using System;
#nullable enable
namespace NonNullableTypeExample
{
public class NonNullableType
{
public string? NonNullableProperty { get; set; }
}
public class TestClass
{
public void TestMethod()
{
NonNullableType? nonNullableType = null;
Console.WriteLine(nonNullableType?.NonNullableProperty);
}
}
}
With the above code, we can get rid of all the compiler warnings. But, we should probably never use this, as it defeats the purpose of this feature.
Can the compiler throw an error instead?
The next question is that is it possible to promote the warning raised due to setting null for nun-nullable types to error? The answer is yes. We can do that using the project-level setting TreatWarningsAsErrors
. But it will make every warning your project as an error. Which might be fine for most cases. And the other important thing to remember is that you still cannot enforce others to do the same. Meaning, if someone else is using your project as a project reference or nuget, they will still get a warning, not an error.
The project setting for throwing an error:
<treatwarningsaserrors>true</treatwarningsaserrors>
Note: The tag TreatWarningsAsErrors
should be inside of a PropertyGroup
tag.
Once we do that, we can run the same example as above and we will get an error : error CS8618: Non-nullable property 'NonNullableProperty' is uninitialized. Consider declaring the property as nullable.
Refresher on Nullable value types
Nullable value types feature was released as a part of the C# 2.0 language release. It was a significant feature as it allowed to easily use SQL data types mapped with C# objects because often primitive types like integer can be null in the database. And to be able to represent the same null value in a nullable integer in C# was a game-changer.
For declaring a nullable integer value type, we will use int? count = null
. And this will initialize the nullable integer to null. Then we can use the HasValue
property to check if it has a value or not. And based on that we can access the value of the integer using the Value
property.
There is a big contrast on how to check nullable value types vs nullable reference types. For example, let’s say we have configured our project to support nullable reference types. In this case, any reference type we declare will be non-nullable. If we have to make them explicitly nullable we can use the ?
just like the nullable value types. But there is no HasValue or Value property available. Instead, we will have to do standard null check for checking if the reference type is null or not.
If we have to take the previous example of the NonNullableType? nonNullableType = null;
we cannot check nonNullableType .HasValue
, instead, we will have to use ?
as in the example Console.WriteLine(nonNullableType?.NonNullableProperty);
Conclusion
In conclusion, I can see the potential of this feature. This definitely helps to express the design intent more clearly through compiler warning. But I am not sure how widely this feature will be used. For brand new applications, it would probably make sense. I would definitely use with feature at project level, as it will help me reduce a lot of NullReferenceException
for sure for missing test cases. Also, this will ensure that I do not have to check for null arguments across the board.
If we can create a Visual Studio project template and set nullable
and TreatWarningsAsErrors
project tags, it will be easier to follow the standard.
Below is the YouTube video where I walk through the examples:
Reference: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references