Back to Blog
C#

C# Nullable Int Usage: Syntax, Checks, and Pitfalls

c# nullable int usage: Learn how to declare, check, and safely read nullable int values in C#. Understand common pitfalls and performance characteristics of int?.

C#nullableintnull handlingvalue types
Diagram showing a nullable int variable with a value and null state, illustrating HasValue and Value access.

Understanding c# nullable int usage starts with the int? syntax, which is a shorthand for Nullable<int>. A nullable int is a value type that can also hold null, making it useful when a variable legitimately has no value—such as a database column that permits NULL or an optional input parameter. Unlike reference types, int? is a struct, so it does not incur heap allocation when used directly.

Declaring and Assigning a Nullable Int

You can declare a nullable int in several ways, all equivalent:

int? a = null; Nullable<int> b = 10; int? c = default; // null

The default value of int? is null, not zero. Assigning a regular int to an int? is an implicit conversion; the value is wrapped in the nullable structure. Assigning null sets the HasValue property to false.

A common mistake is assuming int? behaves like an int with a special sentinel. It does not. The underlying storage is a struct containing both an int value and a boolean flag, but you rarely interact with those fields directly.

Checking Whether a Nullable Int Has a Value

Before reading the value, you must check if it exists. The HasValue property returns true when the nullable contains a non-null value. The Value property returns the underlying int, but throws InvalidOperationException if HasValue is false.

int? maybe = GetOptionalNumber(); if (maybe.HasValue) { Console.WriteLine(maybe.Value); } else { Console.WriteLine("No value"); }

Since C# 7, pattern matching offers a cleaner way:

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

The pattern is int only matches when the nullable has a value, and it extracts the underlying int into a new variable. This avoids the separate HasValue check and is often more readable.

Safely Retrieving the Underlying Value

Calling Value without checking is a frequent source of runtime exceptions. To retrieve the value safely, use GetValueOrDefault() or the null-coalescing operator ??.

int result = maybe.GetValueOrDefault(); // 0 if null int result2 = maybe ?? 0; // 0 if null

GetValueOrDefault() accepts an optional parameter to specify a custom fallback:

int result3 = maybe.GetValueOrDefault(-1);

Both approaches are equivalent for simple fallback logic. The ?? operator is more general because it works with any nullable type and can chain multiple fallbacks.

Nullable Int in Expressions and Conversions

Arithmetic and comparison operators exhibit lifting behavior. When you apply an operator to two nullable ints, the result is also nullable. If either operand is null, the result is null.

int? x = 5; int? y = null; int? sum = x + y; // null

Relational operators return bool, not bool?. If either operand is null, the comparison evaluates to false, except for == and !=, which compare the HasValue and Value properties.

int? a = 5; int? b = null; bool equal = (a == b); // false bool less = (a < b); // false

When converting a nullable int to a non-nullable type, you must explicitly handle the null case. A direct cast throws an exception if the value is null:

int plain = (int)maybe; // InvalidOperationException if maybe is null

Use ?? or GetValueOrDefault() instead.

Common Pitfalls When Using Nullable Int

One recurring mistake is comparing int? to a literal null using == and expecting it to behave like a reference comparison. It works, but only because Nullable<T> overrides Equals and implements ==. However, mixing nullable and non-nullable operands can lead to subtle errors. For example:

int? maybe = null; if (maybe == null) // true { }

This is fine, but avoid using Value without checking HasValue in a multi-threaded context. The value could change between the check and the access. Although Nullable<T> is immutable, the variable itself can be reassigned. If you need a consistent snapshot, copy it to a local first.

Another pitfall is boxing. When you cast int? to object, the result is either null or a boxed int, not a boxed Nullable<int>. This can cause unexpected behavior in generic collections or reflection code.

int? maybe = 5; object boxed = maybe; // boxed int, not Nullable<int>

Performance and Memory Characteristics

Nullable<int> is a struct, so it is allocated on the stack when used as a local variable and does not trigger garbage collection. The size is roughly the size of an int plus a boolean flag, so it is slightly larger than a plain int but still small.

Boxing is the main performance concern. If you store int? in a non-generic collection like ArrayList, it gets boxed. In generic collections like List<int?>, no boxing occurs because the type argument is a struct. Prefer generic collections when you need to store many nullable ints.

The lifting behavior in arithmetic does not add significant overhead; the compiler generates efficient code that checks HasValue before performing the operation. For hot paths, you might prefer explicit checks to avoid the overhead of nullable operations, but the difference is usually negligible unless measured.

When to Choose Nullable Int Over Alternatives

Use int? when the absence of a value is a valid state, such as an optional age field or a database column that allows NULL. For scenarios where you need to distinguish between "not set" and "zero", a nullable int is clearer than using a sentinel like -1 or int.MinValue.

If you are working with legacy code that uses a sentinel, consider refactoring to int? to improve type safety and reduce the chance of accidentally using the sentinel as a real value. For performance-critical code that runs millions of times, measure both approaches; the nullable struct is generally efficient, but a custom struct with a flag might be more explicit if you need to avoid the lifting behavior.

When you need to represent a range of integers plus a special "unknown" state, int? is the idiomatic choice. If you need more than one special state, a custom enum or a wrapper class may be more appropriate, but for most cases int? keeps the code simple and self-documenting.

c# nullable int usage: Practical Usage and Code Examples | RYUSLOG DEV