Back to Blog
C#

C# Nullable Value Type: Syntax and Usage

c# nullable value type: Learn how to declare and use C# nullable value types with Nullable<T>, including HasValue, Value, GetValueOrDefault, and pattern matching.

nullable value typeC# syntaxNullable<T>null handlingvalue types
Diagram showing a nullable value type box with a value and a flag indicating whether a value is present.

c# nullable value type requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When a value type such as int, bool, or DateTime needs to represent a missing or unknown value, C# provides the nullable value type syntax. A nullable value type is an instance of System.Nullable<T> and is declared by appending ? to the underlying type, for example int? or DateTime?. This allows the variable to hold either a valid value of T or the special value null.

Declaring Nullable Value Types

The ? modifier is syntactic sugar for Nullable<T>. The following declarations are equivalent:

int? maybeNumber = null; Nullable<int> alsoMaybeNumber = null;

Both create a value type that wraps an int and a Boolean flag indicating whether a value is present. Because Nullable<T> is itself a struct, a nullable value type is still a value type. It does not introduce heap allocation when stored in a local variable or field, although boxing can occur in certain situations.

You can assign a normal value directly:

int? count = 42;

You can also assign a nullable variable from another nullable variable, and the null state is preserved.

Checking for a Value with HasValue and Value

The Nullable<T> structure exposes two important members: HasValue and Value. HasValue returns true when the variable contains a real value; otherwise it returns false. Accessing Value when HasValue is false throws an InvalidOperationException.

int? maybe = GetNullableInt(); if (maybe.HasValue) { Console.WriteLine($"Value is {maybe.Value}"); } else { Console.WriteLine("No value present"); }

Always check HasValue before reading Value, or use one of the safer access patterns described below.

Using GetValueOrDefault and Null-Coalescing

GetValueOrDefault() returns the underlying value if present, or the default value of T otherwise. You can also supply a custom default:

int? maybe = null; int result = maybe.GetValueOrDefault(-1); // returns -1

The null-coalescing operator ?? provides a more concise alternative:

int result = maybe ?? -1;

Both approaches avoid the exception risk and are common in real-world code. The ?? operator also works with nullable value types in expressions and assignments.

Pattern Matching with Nullable Value Types

C# pattern matching treats a nullable value type as a value pattern. You can use is to check for a value and extract it:

if (maybe is int value) { Console.WriteLine(value); }

This is equivalent to checking HasValue and then reading Value, but the syntax is more readable. You can also combine it with other patterns:

string description = maybe switch { int v when v > 100 => "Large", int v => "Small", null => "None" };

The null pattern matches the case where HasValue is false. This works because Nullable<T> boxes to null when it has no value, and pattern matching handles this transparently.

Boxing and Performance Considerations

When a nullable value type is converted to object or an interface, the CLR boxes it. If HasValue is false, the boxed result is a null reference. If a value is present, the boxed object contains the underlying value, not the Nullable<T> wrapper. This behavior matters when you pass a nullable value type to a method that expects object, or when you store it in a non-generic collection such as ArrayList.

Boxing has a runtime cost. In hot paths, avoid unnecessary conversions. For example, prefer generic collections like List<int?> over non-generic collections, and use ?. and ?? to avoid repeated HasValue checks when possible. The GetValueOrDefault method is implemented without boxing when the underlying type is a primitive, but be aware that passing a nullable value type to a method that accepts object will box it.

Nullable Value Types vs Nullable Reference Types

C# 8 introduced nullable reference types, which are a compile-time annotation feature. They do not change the runtime behavior of reference types; they only enable static analysis warnings. Nullable value types, on the other hand, are a runtime feature that actually changes the type of the variable. The two features solve different problems:

FeatureNullable Value TypesNullable Reference Types
Runtime representationNullable<T> structSame as reference type
Null check enforcementRuntime via HasValueCompile-time warnings
Introduced inC# 2C# 8
Applies toValue types onlyReference types only

Use nullable value types when a value type must represent an absent value, such as a database column that allows NULL. Use nullable reference types to document and enforce that a reference variable should not be assigned null in normal flow.

Common Pitfalls and Edge Cases

One common mistake is comparing a nullable value type directly to null without understanding the underlying behavior. The == operator works, but it is implemented by Nullable<T> and returns true when HasValue is false. This is fine, but be aware that null is not the same as the default value of T. For example, int? with no value is not the same as 0.

Another edge case is lifting operators. When you use arithmetic operators on nullable value types, the result is null if either operand is null. For example:

int? a = 5; int? b = null; int? sum = a + b; // sum is null

This behavior is called lifted operators and applies to most built-in operators. It can be surprising if you expect null to be treated as zero.

Finally, be careful when using nullable value types with as or is in older C# versions. The pattern matching syntax shown above requires C# 7 or later. For earlier versions, stick with HasValue and Value.

c# nullable value type: Practical Usage and Code Examples | RYUSLOG DEV