null means "no value". Reference types (classes, strings, arrays) have always been able to hold it. Value types (int, decimal, bool, DateTime, structs) cannot, unless you make them nullable with ?. C# then gives you a small set of operators for working with values that might be missing: ??, ??= and ?..
Nullable value types: int?
Add ? to a value type to allow null. int? is shorthand for the struct Nullable<int>, which holds a value plus a flag saying whether the value is present:
Output:
False
True
True
5.50
0
3
Rated 4 stars
4
Typical sources of nullable value types are optional form fields, query parameters, and NULL columns in a database: an Order with a DateTime? ShippedAt has not shipped while the value is null. The alternative, a magic value such as -1 or DateTime.MinValue, looks like real data and gets used as one sooner or later.
An int? converts to int only explicitly, because the conversion can fail. That brings us to the one exception nullable value types throw.
"Nullable object must have a value"
Reading .Value when there is no value, or casting a null int? to int, throws InvalidOperationException:
Output:
InvalidOperationException: Nullable object must have a value.
0
0
not stocked
stock is int count is a pattern: it tests for a value and unwraps it into count in one step, which reads better than HasValue plus Value. The pattern matching page has more forms.
Arithmetic and comparisons with null
Operators on nullable value types are lifted: if either operand is null, the result is null. Ordering comparisons (<, >, <=, >=) are the exception: they return false rather than null. (The other exception is bool? with & and |, where null & false is false.) For example:
Output:
True
[]
False
False
True
True
Both a > 5 and a <= 5 are false, so !(a > 5) is not the same as a <= 5 when a is null. Code like if (!(age >= 18)) Deny(); treats a missing age as underage, which may or may not be what you meant. Decide what null should mean first, usually with ??, then compare.
The null-coalescing operator ??
a ?? b evaluates to a when a is not null, and to b otherwise. It works on nullable value types and on reference types, and chains from left to right:
Output:
dark
16px
20
ANONYMOUS
The right-hand side is evaluated only when needed, so cache ?? LoadFromDatabase() does not hit the database when the cache has a value. ?? also accepts a throw expression, which makes a compact guard (see throw):
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
??= (C# 8)
x ??= y assigns y to x only if x is null. It is the idiom for lazy initialization:
private List<string> tags;
public void AddTag(string tag)
{
tags ??= new List<string>(); // C# 8 and later
tags.Add(tag);
}
Before C# 8, write tags = tags ?? new List<string>(); or if (tags == null) tags = new List<string>();.
The null-conditional operator ?.
a?.Member evaluates Member only if a is not null; otherwise the whole expression is null. It turns a chain of null checks into one expression:
Output:
Braga
(no city)
(no customer)
0
True
handler: saved
Details worth knowing:
- The operator short-circuits the rest of the chain: in
nobody?.Address?.City, oncenobodyis null nothing to the right runs. - When the member returns a value type, the result becomes nullable:
Orders?.Countisint?, which is why it needs?? 0to become anintagain. ?.cannot be the target of an assignment before C# 14:customer?.Name = "x"does not compile in earlier versions.handler?.Invoke(...)is the standard way to raise an event or call an optional callback.
Use ?. where null is a legitimate state. Sprinkling it everywhere to avoid exceptions hides the bug that produced the unexpected null, and the program continues with missing data instead of failing where the problem is.
Checking for null
if (customer == null) { ... } // classic
if (customer is null) { ... } // C# 7 constant pattern
if (customer != null) { ... }
if (customer is not null) { ... } // C# 9
if (customer is { } c) { ... } // C# 8 property pattern: not null, and named
is null always performs a real null check, while == calls the type's operator overload if it has one, so a badly written overload can make x == null lie. For arguments that must not be null, fail early:
public void Ship(Order order)
{
ArgumentNullException.ThrowIfNull(order); // .NET 6+
// ...
}
Nullable reference types (C# 8)
Adding ? to a value type changes the type. Adding ? to a reference type, since C# 8, changes only what the compiler checks. With the feature enabled (<Nullable>enable</Nullable> in the project file, the default for new projects since .NET 6), string means "should never be null" and string? means "may be null", and the compiler warns when the code does not match:
#nullable enable
public class User
{
public string Email { get; set; } // warning CS8618: Non-nullable property 'Email'
// must contain a non-null value when exiting constructor.
public string? Nickname { get; set; } // allowed to be null
public int NicknameLength() => Nickname.Length;
// warning CS8602: Dereference of a possibly null reference.
public int SafeLength() => Nickname?.Length ?? 0; // no warning
}
string? input = Console.ReadLine();
string name = input; // warning CS8600: Converting null literal or possible
// null value to non-nullable type.
string checkedName = input ?? "guest"; // no warning
int len = input!.Length; // ! tells the compiler "trust me": no warning
The compiler follows your checks through the method: after if (input != null), input counts as non-null. The ! (null-forgiving) operator silences a warning when you know better than the analysis; each one is a claim the compiler cannot verify, so keep them rare.
Two facts that clear up most confusion. First, this is only warnings: the program runs the same, and a string can still hold null at run time if it came from code without annotations. Second, string? is not Nullable<string>; there is no HasValue on it.
Common mistakes
.Valuewithout a check. ThrowsInvalidOperationExceptionwhen null. Use??,GetValueOrDefault()oris int n.- Assuming
!(x > 5)meansx <= 5. Not whenxis null: every ordering comparison with null is false. - Covering every null with
?.. It silences the symptom and moves the failure further from the cause. - Treating nullable reference warnings as noise. Each one marks a line that can throw
NullReferenceException. Fix the flow or state the intent with?. - Magic values instead of null.
-1,0andDateTime.MinValuefor "unknown" look like real data. Useint?andDateTime?.
Frequently Asked Questions
What does int? mean in C#?
int? is shorthand for Nullable<int>: an int that can also be null. Value types such as int, decimal, bool and DateTime normally always hold a value; adding ? gives them a "no value" state, which is what a missing form field or a NULL database column needs. Check it with HasValue or != null and read it with Value or GetValueOrDefault().
What does ?? do in C#?
a ?? b is the null-coalescing operator: it evaluates to a if a is not null, otherwise to b. string name = input ?? "guest"; supplies a fallback in one expression. It chains (a ?? b ?? c takes the first non-null value) and b is only evaluated when needed. ??= (C# 8) assigns the right side only when the variable is null.
What does ?. do in C#?
a?.B is the null-conditional operator: if a is null, the whole expression is null and B is never evaluated, so there is no NullReferenceException. It chains (order?.Customer?.Address?.City) and works for method calls (logger?.Log(...)), events (Changed?.Invoke(...)) and indexers (items?[0]). Combine it with ?? for a default: order?.Customer?.Name ?? "unknown".
What causes "Nullable object must have a value"?
Reading .Value of a nullable value type that is null, or casting it to the non-nullable type ((int)maybe), throws InvalidOperationException with that message. Check HasValue first, or use GetValueOrDefault(), ?? fallback, or a pattern: if (maybe is int n) { ... }.
How do I check for null in C#?
x == null and x is null both work; is null (C# 7) cannot be affected by a type that overloads ==, so many codebases prefer it. The negation is x != null, or x is not null in C# 9. To reject null arguments, ArgumentNullException.ThrowIfNull(x) (.NET 6) or _ = x ?? throw new ArgumentNullException(nameof(x)); in older code.
What are nullable reference types in C#?
A C# 8 compiler feature, turned on with <Nullable>enable</Nullable> (the default in new .NET 6+ projects). With it on, string means "never null" and string? means "may be null", and the compiler warns when you might dereference null (CS8602) or assign null to a non-nullable variable (CS8600). It changes only warnings, not run-time behavior.