C# Nullable value types: Everything you need to know

A thumbnail showing code. Everything you need to know about C# Nullable value type as a web developer.

Null references are the bane of every developer's existence. They can occur anywhere, anytime and cause many hard to find bugs that cost time and money to fix.

While there may be some debate whether null references should exist, they do and it's important to understand how to deal with them lest you run into problems when running your code.

The .NET framework has a solution for this problem in the form of nullable value types.

C# Nullable value types is a type representation that allows a variable to contain either a value or null.

A nullable type is created by adding ? after the type name. For example, int? means that value can either be an integer or null.

In this article, we'll discuss the basics of nullable value types and how to use them.

An example code of C# Nullable type usage.
C# Nullables are useful for showing absence of value.

Nullable value type vs reference type

It's important to know the difference between a nullable value type and nullable reference type:

  • Reference types support the null value.
  • Value-type variables don't support the null value without using the nullable value type.

The nullable value types are available beginning with C# 2. But C# 8.0 introduces the nullable reference types feature.

The difference between nullable value type and nullable reference type is that a nullable reference type can refer to an instance of any reference type, while a nullable value type can only hold a value of the underlying type.

Nullable value type

For example, if we try to set a nullable integer to null, we'll see the following error:

int someInt = null;

Cannot convert null to 'int' because it is a non-nullable value type.

If we try to do the same using a nullable value type, we'll see that it's possible:

int? someInt = null;

Nullable reference type

For reference types, null is a valid value. For example, we can set a string to null:

string someString = null;

But with nullable reference types feature, the C# compiler will warn us of possible null references.

Nullable reference types won't make your code bulletproof. There is no difference in runtime between a non-nullable reference type and a nullable reference type. The compiler doesn't add any runtime checking for non-nullable reference types. The benefits are in the compile-time analysis.

Nullable reference types are not distinct new class types but annotations on existing reference types. The compiler uses them to show warnings on null reference mistakes in your code.

Why use nullable types?

In C#, nullable value types are great for explicitly representing variables that may or may not have a value. Nullable types make that intent clear and remove any ambiguity. This allows for better code readability and developer productivity.

For example, the default value for int is 0. But 0 could actually present a valid value. This can lead to many bugs that are difficult to track down.

With nullable types, you can set the variable to null if it doesn't have a value. This makes it clear that the variable doesn't have a value and can help avoid errors.

For example:

csharp
int? page = null;

Console.WriteLine(page .HasValue); // False

Difference between Nullable<T> and ? annotation

Nullable<T> and ? are the same. The ? annotation is a shortcut for Nullable<T>.

The Nullable<T> type is just a wrapper around the actual value that will contain null values unless otherwise specified. It is not a new type and it will behave the same as the underlying type.

System.Nullable class

To use nullable types in C#, you can reference the System.Nullable class, which contains the following:

  • Value property - Gets or sets the value of this nullable type.

  • HasValue property - Shows whether the current instance has a non-null value.

  • GetValueOrDefault() method - Gets the value of this nullable type if it has a value; otherwise returns the default value for this type.

The core of the nullable wrapper is very simple:

csharp
...
public struct Nullable<T> where T : struct
    {
        private bool hasValue; 
        internal T value;
 
        [System.Runtime.Versioning.NonVersionable]
        public Nullable(T value) {
            this.value = value;
            this.hasValue = true;
        }        
 
        public bool HasValue {
            [System.Runtime.Versioning.NonVersionable]
            get {
                return hasValue;
                }
            } 
 
        public T Value {
            get {
                if (!hasValue) {
                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
                }
                return value;
            }
        }
...

This code is very straightforward:

  • “value” variable stores the actual value of the instance and is set by the constructor, using parentheses and specifying the type directly.

  • Getters for “HasValue” property just return a boolean indicating whether there is a value to return. They don't actually do anything else with it.

We can also see that underlying type must be a struct. If you try to use Nullable<T> with a value type that is not a struct, you will get a compile-time error.

Nullable.GetUnderlyingType(Type) Method

The Nullable.GetUnderlyingType(Type) method returns the underlying type of a nullable type.

This method can be used to determine if a nullable type is an instance of a specific type. It can also be used to cast a nullable type to its underlying type.

This method is useful when you need to do the following:

  • Determines whether a type can be cast to a specific type.

  • Cast the nullable type to its underlying type.

The source code for C# Nullable.GetUnderlyingType method.
Source code for Nullable.GetUnderlyingType(Type) method.

C# Nullable Unwrapping

C# Nullable unwrapping is a process of accessing the underlying value.

We can use the Nullable<T>.GetValueOrDefault() method to unwrap a nullable type and return the underlying value. If the nullable type has a value, the method will return the value. If the nullable type is null, the method will return the default value for the type.

Null-coalescing operator ??

The null-coalescing operator ?? is a binary operator that returns the first operand if it is not null; otherwise, it returns the second operand.

The null-coalescing operator is useful when working with the Nullable type. If you want to assign a non-nullable value type variable a value of a nullable value type, you'll need to provide the replacement for null instead. For example:

csharp
int DEFAULT = 5;

int? num = null; 

int result = num ?? DEFAULT ; 

Console.WriteLine(result) // 5.   

Alternatively, we can use GetValueOrDefault() method to pass the default value:

csharp
int DEFAULT = 5;

int? num = null; 

int result = num.GetValueOrDefault(DEFAULT); 

Console.WriteLine(result) // 5.   

Both examples will print "5".

How to identify a nullable value type

Identifying a nullable value type at runtime is not intuitive.

To determine whether an instance is of a nullable value type use Nullable.GetUnderlyingType(Type nullableType) method.

If the type provided is not a Nullable Type, Nullable.GetUnderlyingType returns null. Otherwise, returns the underlying type of the Nullable type.

Use the extension method To make it easy to check for nullable value types, use the following extension method:

csharp
public static bool IsOfNullableType<T>(this T _)
{

		Type type = typeof(T);

  		return Nullable.GetUnderlyingType(type) != null;

}

int? num = null; 

Console.WriteLine(num.IsOfNullableType()); // True 

Don't use Object.GetType

Don't rely on the Object.GetType method to determine whether a particular object is of a nullable value type. Calling the Object.GetType method on an instance of a nullable value type boxes it to Object. When boxing a value of a non-null instance of a nullable value type, the underlying type is represented by the Type returned by GetType:

Don't use is operator

The is operator cannot be used to identify a nullable value type. The is operator returns true if the instance is of the specified type and false if the instance is not of the specified type. However, with nullable value types, an instance can be of the specified type or nullable.

Example nullable types use-case

Default value

Nullable provide more explicit way of indicating that a variable is allowed to be null.

Having a nullable type shows an intent to pass on data that might or might not exist. It makes intent more explicit, rather than leaving it up to a consumer of the value to discern whether some field is legal to be null.

For example, in C#, enums cannot be null because enums are non-nullable value type. Enums default to 0, which can cause unexpected behavior when you try to use an enum whose value has not been initialized.

For example, in the code below, the grade will default to A. This might lead to unexpected behavior if the developer of this code doesn't realize that an uninitialized grade was used:

csharp
public enum Grade
{
		A,
		B,
		C,
		D,
		F,
}

public static void Main()
{

		Grade grade = default;

		Console.WriteLine(grade); // A

}

That's why using a nullable value type will make the intent more explicit:

csharp
Grade? grade = default;

Console.WriteLine(grade == null); // True

Database columns

Nullable types are useful when working with databases. For example, in SQL Server, there is a concept of nullable columns. A nullable column can contain a value or it can be NULL. Nullable value types in C# can represent the NULL value similarly.

Optional argument

In ASP.NET controllers, nullable value types are useful for optional parameter values. For example, an API endpoint takes an optional page number parameter. If the page number is not supplied, the default value (should be used. In C#, this can be implemented using a nullable type:

csharp
public IActionResult Get([FromQuery] string search, [FromQuery] int? page)
{
    _searchService.FetchResults(search, page ?? 1);
}

Start using nullable in C#

C# nullable value types provide a way to work with data values that may be null.

Understanding the difference between nullable value type and nullable reference type will make you a better C# developer.

They are useful when you need to check for a value, or when you need to provide a default value. They make intent more explicit, and can be used when working with databases or optional arguments. You can identify a nullable value type using the Nullable.GetUnderlyingType extension method. Also, don't use Object.GetType and isoperator to identify a nullable value type.

Nullable types in C# are important when you need to handle exceptional cases, such as when a value doesn't exist. Here, you can use the null-coalescing operator ?? to get the value or provide a default value.

Start using nullable types in your own code to make your intentions more explicit and to avoid potential bugs.

Published on