C# nullable types — int?, Nullable<T>, null handling patterns
Value types like int, bool, and DateTime can't normally be null — they always hold a value. Nullable value types (written as int?, bool?, DateTime?) wrap a value type in Nullable<T>, adding a null state to represent "no value."
This is essential when working with databases (where columns can be NULL), optional fields, or any scenario where the absence of a value is meaningful.
C# Example Code
int a = null; // ❌ compile error — int can't be null
int? b = null; // ✅ nullable int — has no value
int? c = 42; // ✅ nullable int — has value 42HasValue and Value
C# Example Code
int? age = null;
Console.WriteLine(age.HasValue); // False
// age.Value would throw InvalidOperationException — never call it without checking!
age = 30;
Console.WriteLine(age.HasValue); // True
Console.WriteLine(age.Value); // 30Always check HasValue (or != null) before accessing .Value.
GetValueOrDefault
Returns the value if present, or a fallback if null — without throwing.
C# Example Code
int? score = null;
int result1 = score.GetValueOrDefault(); // fallback = 0 (type default)
int result2 = score.GetValueOrDefault(-1); // fallback = -1
int result3 = score ?? 0; // same as GetValueOrDefault(0)
Console.WriteLine(result1); // 0
Console.WriteLine(result2); // -1
score = 85;
Console.WriteLine(score.GetValueOrDefault(-1)); // 85Null Checks
C# Example Code
int? quantity = GetQuantityFromDatabase(); // might be null
// All three are equivalent:
if (quantity.HasValue) { /* use quantity.Value */ }
if (quantity != null) { /* use quantity.Value or quantity */ }
if (quantity is int q) { /* q is the unwrapped int */ }
// Pattern matching (C# 9+) — clearest for complex conditions
if (quantity is >= 1 and <= 100)
Console.WriteLine("Valid quantity");Null-Coalescing Operators
C# Example Code
int? timeout = null;
int actual = timeout ?? 30; // ?? — use 30 if null
timeout ??= 30; // ??= — assign 30 if null, keep existing value
string label = timeout?.ToString() ?? "none"; // ?. safe navigation + ??
Console.WriteLine(actual); // 30
Console.WriteLine(timeout); // 30
Console.WriteLine(label); // 30Arithmetic with Nullable Types
Any arithmetic involving a null nullable produces null — it propagates automatically.
C# Example Code
int? a = 10;
int? b = null;
Console.WriteLine(a + 5); // 15
Console.WriteLine(a + b); // (null)
Console.WriteLine(a * b); // (null)
Console.WriteLine(b ?? 0); // 0 — use ?? to collapse null before mathComparisons
C# Example Code
int? x = 5;
int? y = null;
Console.WriteLine(x > 3); // True
Console.WriteLine(y > 3); // False — null comparisons always return false (except !=)
Console.WriteLine(y == null); // True
Console.WriteLine(y != 5); // TrueNullable in Classes and Records
C# Example Code
public class Employee
{
public string Name { get; set; } = "";
public DateTime? TerminationDate { get; set; } // null = still employed
public decimal? Bonus { get; set; } // null = not yet determined
}
var emp = new Employee { Name = "Alice" };
string status = emp.TerminationDate.HasValue
? $"Left on {emp.TerminationDate.Value:d}"
: "Currently employed";
Console.WriteLine(status); // Currently employed
decimal pay = 50_000 + (emp.Bonus ?? 0);
Console.WriteLine(pay); // 50000Nullable Value Type vs Nullable Reference Type
These are two separate features — don't confuse them:
Nullable value type (int?) | Nullable reference type (string?) | |
|---|---|---|
| Introduced | C# 2 | C# 8 |
| Purpose | Allow value types to represent null | Annotate reference types as intentionally nullable |
| Runtime representation | Nullable<T> struct | Same string — annotation only |
| Checked at | Runtime (HasValue) | Compile-time (nullable warnings) |
| Default behavior | Opt-in (always available) | Opt-in via <Nullable>enable</Nullable> |
C# Example Code
// Nullable value type — runtime concept
int? count = null;
// Nullable reference type — compile-time warning annotation
string? name = null; // "I know this can be null"
string id = null; // ⚠️ warning: non-nullable assigned nullCommon Patterns
Lift a value type to nullable for optional parameters
C# Example Code
// Optional date range filter
static IEnumerable<Order> GetOrders(DateTime? from = null, DateTime? to = null)
{
var orders = AllOrders();
if (from.HasValue) orders = orders.Where(o => o.Date >= from.Value);
if (to.HasValue) orders = orders.Where(o => o.Date <= to.Value);
return orders;
}Convert nullable to non-nullable safely
C# Example Code
int? raw = GetRawValue();
// Option 1 — ?? with a safe default
int value = raw ?? 0;
// Option 2 — guard and throw a meaningful error
int value2 = raw ?? throw new InvalidOperationException("Expected a value.");
// Option 3 — pattern matching
if (raw is int v)
{
// use v
}LINQ with nullable
C# Example Code
var records = new List<int?> { 1, null, 3, null, 5 };
// Filter nulls and unwrap
var values = records
.Where(x => x.HasValue)
.Select(x => x!.Value)
.ToList();
Console.WriteLine(string.Join(", ", values)); // 1, 3, 5
// Sum ignoring nulls
int sum = records.Sum(x => x ?? 0);
Console.WriteLine(sum); // 9