In this article, we are going to discuss the null! statement in C#. First, we will explore its construct, and function in our programs. Then, we will examine various situations where we can use this statement in our applications.
Let’s dive in.
A Little Bit of History – The Different Types of Objects in C#
For us to properly examine the null!
statement, we have to first understand the different types of objects we have in C# and their relationships with null.
Generally, objects in C# can either be value types or reference types. Let’s briefly look at what these object types entail.
Value Types
Value types are variables that directly hold their values in the stack memory. These types inherit from the System.ValueType
class and we create them using the struct
keyword.
By default, value types cannot be null. However, they can become null if we declare them as nullable value types. Therefore, whenever we create a non-nullable value type without assigning a value to it, it automatically defaults to a non-null value.
Value types in C# include int
, float
, bool
, and DateTime
.
Reference Types
On the other hand, reference types are variables that hold references to the actual data. These references are stored in the stack, while the actual data resides in the heap. Examples of reference types in C# include strings and classes.
Now, unlike value types that can never be null, reference type variables can be null. Whenever we declare a reference type without assigning a value to it, it defaults to null.
Therefore, accessing reference types becomes something that we need to do with caution to avoid raising the NullReferenceException
in our apps.
To address this challenge and reduce the number of potential issues that may arise from accessing null reference types, nullable reference types were introduced in C# 8.
Nullable Reference Types
Nullable reference types are a set of features that statically analyze our programs and track the way we utilize null in them. These features help us reduce the occurrences of the NullReferenceException
in our applications. They identify and warn us of potential null errors at compile time.
For a clearer understanding of what nullable reference types do for us, let’s go through some code examples.
For this, let’s first look at how we defined properties before the introduction of nullable reference types:
public class Student { public int Id { get; set; } public string Name { get; set; } }
In this Student
class, we first define a value type integer property, Id
. Then, we declare a reference type string
property, Name
, without any warnings from the compiler.
We could do this because our codes were nullable-oblivious. This means we could freely assign null or potentially null expressions to variables without triggering compiler warnings. For instance, leaving our string property uninitialized as we did in our Student
class, thereby allowing it to default to null, was perfectly acceptable to the compiler.
However, with the advent of nullable reference types, defining classes this way doesn’t go without some compiler warnings.
With the introduction of nullable reference types, the compiler now treats all our reference type variables as non-nullable. In this case, it sees our string
property, Name
, as a non-nullable type.
To make the compiler start seeing them as nullable types again, we now have to use the nullable (?) operator:
public string? Name { get; set; }
However, if we decide to leave them as non-nullable types, in this case, leave our Name
property as a string
, then the compiler will require us to assign an initial value to it.
Failure to assign this initial value means we want to allow a non-nullable property to have a null value since it will automatically default to null. This action will trigger the CS8618
null warning from the compiler.
The CS8618
warning tells us that our reference type property could become null at runtime, and could eventually result in the NullReferenceException
being thrown, which may cause our app to behave in an unexpected manner.
To address this issue and suppress the warning, one effective approach is to utilize the null!
statement.
Let’s dive deep and critically explore how to use the null!
statement to carry out this task.
Using The null! Statement to Suppress The CS8618 Warning
To suppress this warning with the null!
statement, we just have to assign it to our property:
public class Student { public int Id { get; set; } public string Name { get; set; } = null!; }
Immediately after we do this, the CS8618
null warning from the compiler disappears.
Cool! But how does this work?
The null-forgiving Operator
To understand how the null!
statement performs this suppression, let’s first examine its syntax.
To get the null!
statement, we attach the unary postfix operator, !
, to the end of the null keyword. This !
operator, is the null forgiving operator, and it suppresses null warnings from the compiler by marking a nullable type as a non-nullable type.
In this case, by attaching it to the null literal to get null!
, we tell the compiler that we want to mark the null literal as a non-nullable value, even though we know it is nullable by default.
Okay. So, what does assigning this null!
statement to our property do?
The null! Statement
When we initialize our non-nullable reference type property to this non-nullable null, null!
, we prevent our reference types from defaulting to null
, a nullable value.
With that, we obey the compiler’s rule of making sure our non-nullable property contains a non-null value before exiting the constructor. With this assignment, we tell the compiler that our property won’t be null.
Awesome! As we’ve seen, this technique works perfectly, and with it, we’ve successfully suppressed the CS8618
warning.
However, we should always remember that since reference types always default to null, this suppression only happens at compile-time. It doesn’t have any effect at runtime. Therefore, even after suppressing the compiler’s warnings with the null!
statement, we still risk encountering the NullReferenceException
during runtime if our reference type property ever becomes null.
Therefore, we should limit our usage of this statement to prevent unexpected exceptions when our apps are running.
With that in mind, let’s conclude by outlining some scenarios where the null!
statement can be useful to us.
Situations That May Require Us to Utilize the null! Statement
Firstly, as we’ve seen in our example, we can use null!
to suppress null warnings from the compiler. This is useful in situations where we have a non-nullable reference type that we want to remain non-nullable, but are not assigning a value in the constructor, but will instead initialize it at a later time, while our app is running.
Additionally, we can use this statement when we wish to define required (not null) properties for our models when working with EF Core. However, we should note that marking our properties as required members is a more preferred way of handling this task.
Conclusion
In this article, we’ve thoroughly explored the null! statement in C#.
We discussed how to use it, how it works, and when to use it in our applications.
Great articule, I ask you about the required statement. What is the diferent to mark requirente using null! ?
Hi Sergio.
First, thank you for the great review.
Now, to your question.
As we stated in the article, we can use both the required keyword and the null! statement to mark our properties as not null when working with EF Core.
However, the required keyword is preferred because it forces us to assign a value to a required property during instantiation. If we try to create an instance of our model without assigning a value to our required property, it will raise an exception.
In contrast, the null! statement doesn’t do this. With the null! statement, we can leave our property without a value during instantiation, and we won’t get any warnings or exceptions.