Nullable Types
null means "no value". It's useful — a customer may have no middle name, a search may find nothing — but reading a member of null crashes with a NullReferenceException. C# tackles this from two sides: nullable value types let numbers and other structs be null when you need it, and nullable reference types make the compiler warn you wherever a null could slip through.
Nullable value types
Value types like int and DateTime can't normally be null. Add ? to allow it — int? is short for Nullable<int>:
int? rating = null; Console.WriteLine(rating.HasValue); Console.WriteLine(rating ?? 0); Console.WriteLine(rating.GetValueOrDefault(-1)); rating = 4; Console.WriteLine(rating.HasValue); Console.WriteLine(rating.Value); Console.WriteLine(rating + 1); int? missing = null; Console.WriteLine(missing + 1 is null); Console.WriteLine(missing > 3);
False 0 -1 True 4 5 True False
Arithmetic with a null operand gives null, and comparisons with null are false. Reading .Value when there's no value throws an InvalidOperationException, so test first or use ??.
Nullable value types are ideal for optional data, such as a database column that may be empty:
var p1 = new Patient("Ana", 1990); var p2 = new Patient("Ben", null); foreach (var p in new[] { p1, p2 }) { string age = p.BirthYear is int year ? $"{2026 - year}" : "unknown"; Console.WriteLine($"{p.Name}: age {age}"); } record Patient(string Name, int? BirthYear);
Ana: age 36 Ben: age unknown
Nullable reference types
Reference types such as string and classes could always hold null. With nullable reference types — on by default in every new project and single-file app — the compiler tracks which variables might be null:
stringmeans "should never be null".string?means "might be null".
The compiler then warns you about code that could dereference null or store null where it doesn't belong. These are warnings, not errors, so the program still builds — but each one is a potential crash:
string? middleName = GetMiddleName(); string copy = middleName; Console.WriteLine(middleName.Length); static string? GetMiddleName() => null;
Program.cs(2,15): warning CS8600: Converting null literal or possible null value to non-nullable type. Program.cs(3,19): warning CS8602: Dereference of a possibly null reference. Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
Flow analysis: checking removes the warning
The compiler follows your code. After a null check, it knows the variable is safe:
PrintLength("hello"); PrintLength(null); static void PrintLength(string? text) { if (text is null) { Console.WriteLine("(no text)"); return; } Console.WriteLine(text.Length); // no warning: text can't be null here }
5 (no text)
Prefer is null and is not null to == null: they can't be affected by an overloaded == operator.
The null operators
| Operator | Meaning |
|---|---|
a?.B | Access B only if a isn't null; otherwise the result is null |
a?[i] | Index only if a isn't null |
a ?? b | a if it isn't null, otherwise b |
a ??= b | Assign b to a only if a is null |
a?.B = c | Assign only if a isn't null (C# 14) |
a! | "Trust me, this isn't null" — silences the warning |
Customer? customer = FindCustomer("c-42"); Customer? nobody = FindCustomer("c-99"); Console.WriteLine(customer?.Address?.City ?? "no city"); Console.WriteLine(nobody?.Address?.City ?? "no city"); Console.WriteLine(nobody?.Orders?[0] ?? "no orders"); customer?.Address?.City = "Porto"; // C# 14: null-conditional assignment nobody?.Address?.City = "Porto"; // does nothing, no crash Console.WriteLine(customer?.Address?.City); List<string>? tags = null; tags ??= []; tags.Add("vip"); Console.WriteLine(tags.Count); static Customer? FindCustomer(string id) => id == "c-42" ? new Customer { Address = new Address { City = "Lisbon" }, Orders = ["A1"] } : null; class Customer { public Address? Address { get; set; } public List<string>? Orders { get; set; } } class Address { public string City { get; set; } = ""; }
Lisbon no city no orders Porto 1
Chained ?. stops at the first null, so customer?.Address?.City never crashes, whatever is missing.
Non-nullable properties must be initialised
If a class has a non-nullable reference property, the compiler wants proof it gets a value — otherwise it would start as null:
var u = new User(); Console.WriteLine(u.Email.Length); class User { public string Email { get; set; } }
Program.cs(6,19): warning CS8618: Non-nullable property 'Email' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
Fix it in whichever way matches the meaning:
- give it a default:
public string Email { get; set; } = ""; - set it in a constructor;
- mark it
requiredso callers must supply it; - or declare it
string?if "no email" is a valid state.
The null-forgiving operator
Sometimes you know better than the compiler — for instance, a value was validated earlier in a way it can't see. A postfix ! suppresses the warning:
var settings = new Dictionary<string, string?> { ["theme"] = "dark" }; string theme = settings["theme"]!; // we just put it there Console.WriteLine(theme.ToUpper());
DARK
Use ! rarely. It doesn't check anything at run time; if you're wrong, you still get a NullReferenceException — you've just removed the warning that would have told you.
Guarding public methods
Annotations only produce warnings, and callers in other projects may ignore them. Public methods that require a non-null argument should still check, and ArgumentNullException.ThrowIfNull makes it a one-liner:
Console.WriteLine(Initials("Grace Hopper")); Console.WriteLine(Initials(null!)); static string Initials(string fullName) { ArgumentNullException.ThrowIfNull(fullName); return string.Concat(fullName.Split(' ').Select(part => part[0])); }
GH Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'fullName')
Similar helpers exist for other checks: ArgumentException.ThrowIfNullOrEmpty, ThrowIfNullOrWhiteSpace, and ArgumentOutOfRangeException.ThrowIfNegative.
Try it yourself
Write FindUser, which returns null when no user matches, and handle both cases without any warnings.
Ben Okafor (Lagos) Ana Lima (city not given) zoe: not found