C# Nullable Operators: Syntax and Usage
c# nullable operators: Learn how C# nullable operators like ?., ??, and ??= simplify null handling, avoid NullReferenceException, and make code more readable.
The Null-Conditional Operator (?.)
C# nullable operators provide a concise way to handle null values without scattering checks throughout your code. The null-conditional operator (?.) lets you access members and elements only when the receiver is not null. If the receiver is null, the entire expression evaluates to null instead of throwing a NullReferenceException. This operator is essential for safely navigating object graphs that may contain null values.
string? name = GetName(); int? length = name?.Length;
If name is null, length is null. The operator works for properties, methods, indexers, and delegates. It is particularly useful for chaining calls, as the expression short-circuits as soon as a null is encountered.
var order = GetOrder(); string city = order?.Customer?.Address?.City ?? "Unknown";
Here, if order, Customer, Address, or City is null, the entire chain returns null, and the null-coalescing operator provides a default. This avoids nested if checks and keeps the code linear.
The Null-Coalescing Operator (??)
The null-coalescing operator (??) returns the left-hand operand if it is not null; otherwise, it returns the right-hand operand. It is a concise way to provide a default value without an explicit conditional statement.
string displayName = name ?? "Guest";
The right-hand operand is only evaluated when the left-hand operand is null, which is important when the fallback involves an expensive operation or a function call. The operator also works with nullable value types:
int? maybeNumber = GetNumber(); int actual = maybeNumber ?? 0;
This converts a nullable value type to a non-nullable type while supplying a default. The types must be compatible; the right-hand operand must be implicitly convertible to the left-hand operand's underlying type.
The Null-Coalescing Assignment Operator (??=)
The null-coalescing assignment operator (??=) assigns the right-hand operand to the left-hand operand only if the left-hand operand is null. It is a shorthand for if (variable == null) variable = value; and is particularly useful for lazy initialization.
List<int>? numbers = null; numbers ??= new List<int>(); numbers.Add(42);
This operator reduces boilerplate and makes the intent explicit. It works with both reference types and nullable value types. The right-hand side is evaluated only when the left-hand side is null, preserving the same lazy evaluation behavior as ??.
Combining Nullable Operators in Real Scenarios
The operators are often used together to build robust null-handling pipelines. Consider a configuration object with optional nested properties:
string timeout = config?.Timeout?.ToString() ?? "30";
You can also combine them with pattern matching and other C# features. For example, you might use a null-conditional operator to safely invoke an event:
EventHandler? handler = OnChanged; handler?.Invoke(this, EventArgs.Empty);
This pattern is common in event-raising code and avoids a race condition between the null check and the invocation.
Common Mistakes and Subtle Behavior
One common mistake is assuming that the null-conditional operator prevents all null-related errors. It only prevents exceptions when the receiver is null. If you use the result in a context that does not accept null, you may still get a compile-time warning or a runtime issue. For instance, assigning the result of name?.Length to a non-nullable int will cause a compile-time error because the result is int?.
Another subtlety: the null-coalescing operator does not convert between unrelated types. The right-hand operand must be implicitly convertible to the left-hand operand's type. If you try string result = maybeInt ?? "default";, you will get a compile-time error because int and string are not compatible.
Performance and Operational Considerations
The null-conditional operator compiles to a simple null check, so the runtime overhead is negligible. However, chaining many null-conditional operators can make code harder to read and debug. In performance-critical paths, consider whether a single null check is clearer and faster than a long chain.
The null-coalescing operator's right-hand operand is evaluated lazily, which can avoid expensive calls when the left side is not null. For example, value ?? ComputeDefault() does not call ComputeDefault() unless value is null. This is a useful optimization when the default is costly to produce.
Nullable Value Types and Reference Types Context
C# nullable operators work with both nullable value types (int?) and nullable reference types (enabled via #nullable). For reference types, the compiler provides warnings to help you avoid null dereferences, but the operators still work as expected.
Understanding the context of nullable reference types is essential for writing modern C# code that clearly communicates nullability intent. When you enable nullable reference types, the compiler tracks whether a reference can be null, and the nullable operators become even more valuable because they allow you to handle null without suppressing warnings.
For example:
#nullable enable string? name = GetName(); int length = name?.Length ?? 0;
The compiler understands that name may be null, and the null-conditional operator safely handles that case. This combination of nullable reference types and nullable operators leads to more predictable code and fewer unexpected NullReferenceExceptions in production.