Back to Blog
C#

C# Null Conditional Operator: Syntax and Behavior

c# null conditional operator: Learn how the C# null conditional operator (?. and ?[]) works, including short-circuiting, value types, common mistakes, and runtime beha...

null safetyC# 6nullable typesshort-circuitingnull handling
Illustration of a chain of object nodes where a null node is safely bypassed by the C# null conditional operator.

The C# null conditional operator (?.) provides a concise way to access members or elements of an object only when that object is not null. Instead of writing an explicit null check before every dereference, the operator short-circuits the expression and produces null when the receiver is null.

string? name = customer?.Name;

If customer is null, name is null. If customer is not null, name receives customer.Name. The expression never throws a NullReferenceException because of a null customer.

The same operator has an indexed form, ?[], for collections:

string? first = items?[0];

When items is null, first is null rather than the expression throwing.

How the Operator Short-Circuits

The null conditional operator does not simply check for null at each step. It short-circuits the entire remainder of the expression. Once the receiver evaluates to null, the rest of the chain is not evaluated at all.

int? length = customer?.Address?.City?.Length;

If customer is null, neither Address, City, nor Length is evaluated. The whole expression returns null. This matters when later members have side effects, such as property getters that perform logging or lazy initialization. Those getters are skipped entirely.

The short-circuiting also applies to method invocation:

customer?.Save();

If customer is null, Save is not called. The expression returns null (which is fine for a void method call).

Chaining with the Null Coalescing Operator

The null conditional operator is frequently combined with ?? to provide a fallback value when the chain produces null.

string displayName = customer?.Name ?? "Unknown";

Here, if customer is null or customer.Name is null, displayName receives "Unknown". The combination reads naturally: attempt the safe navigation, then supply a default.

This pattern is common when mapping domain objects to view models or DTOs:

var dto = new CustomerDto { Name = customer?.Name ?? string.Empty, Email = customer?.Contact?.Email ?? string.Empty };

Null Conditional with Value Types

When the member being accessed is a value type, the result of the null conditional operator is a nullable version of that type.

int? age = customer?.Age;

Age is an int, but the expression produces int? because customer might be null. This is a common source of confusion. You cannot assign the result directly to a non-nullable int:

int age = customer?.Age; // Compiler error

You must either use .GetValueOrDefault(), the null coalescing operator, or handle the nullable explicitly.

The same applies to the indexed form with value types:

int? firstNumber = numbers?[0];

Common Mistakes and Edge Cases

One frequent mistake is assuming the null conditional operator protects against nulls inside the accessed member. It protects only the receiver of the operator, not the result.

string? name = customer?.Name; int length = name.Length; // Still throws if name is null

The operator returns null when the receiver is null, but the returned value can itself be null. If the member is a reference type that is null, the result is null regardless of the operator.

Another edge case is the indexed form on a null array. ?[] returns null, but it does not check whether the index is in range. An out-of-range index on a non-null collection still throws ArgumentOutOfRangeException or IndexOutOfRangeException.

string? item = items?[10]; // Safe only if items is null; throws if items has fewer than 11 elements

The operator also cannot be used in all contexts. For example, you cannot use it directly in a ref or out argument, and you cannot assign to a null conditional expression:

customer?.Name = "New"; // Compiler error

Performance and Runtime Behavior

The null conditional operator compiles to a sequence of explicit null checks and conditional branches. There is no reflection, no dynamic dispatch, and no hidden allocation beyond the nullable wrapper for value types. For reference types, the generated IL is essentially equivalent to:

string? name = customer != null ? customer.Name : null;

The JIT can often optimize this to a simple branch. The cost is negligible in typical application code. The main performance consideration is not the operator itself but the short-circuiting behavior: because the rest of the chain is skipped, expensive property getters are not invoked when the receiver is null. This can actually reduce work compared to a naive implementation that dereferences step by step.

One subtle runtime behavior is that the operator does not catch exceptions thrown by the accessed member. If the customer.Name getter throws, the exception propagates. The operator only handles null, not exceptions.

Maintainability and When to Use It

The null conditional operator improves readability when null checks would otherwise add several lines of boilerplate. It is most valuable in chains of related accesses, such as navigating nested object graphs.

It is less useful when the logic requires different handling for each null level. For example, if you need to distinguish between "customer is null" and "customer exists but has no address", the operator collapses both cases to null. In that situation, explicit null checks with separate branches are clearer.

if (customer is null) { // Handle missing customer } else if (customer.Address is null) { // Handle missing address }

The operator is also not a substitute for validating input at boundaries. Using ?. throughout a codebase can hide the fact that a null value arrived where it should not have. For public APIs and deserialization boundaries, explicit validation is often more appropriate.

Compatibility and Language Version

The null conditional operator was introduced in C# 6.0. Code that uses it requires a compiler that supports C# 6 or later. The runtime does not need any specific version because the operator is purely a compile-time transformation; it produces standard IL that runs on any .NET Framework, .NET Core, or .NET 5+ runtime that supports the target framework.

This means the operator works in older runtime environments as long as the compiler emits compatible IL. It is safe to use in libraries targeting .NET Framework 4.x, for example, provided the build toolchain supports C# 6.

The indexed form ?[] was also introduced in C# 6.0, so there is no version difference between the member access and index access forms.

c# null conditional operator: Practical Usage and Code Examp | RYUSLOG DEV