C# Null-Conditional Operators: ?. and ?[]

published on 23 October 2024

Tired of null reference errors in your C# code? Here's what you need to know about the null-conditional operators (?. and ?[]):

These operators let you safely access properties and arrays without writing endless null checks. Here's how they work:

// Instead of this:
if (person != null && person.Address != null) {
    city = person.Address.City;
}

// Write this:
city = person?.Address?.City;

Key Benefits:

  • Prevents null reference exceptions
  • Makes code shorter and cleaner
  • Thread-safe for event handling
  • Almost zero performance impact
Operator Use Case Example
?. Safe property access person?.Name
?[] Safe array access array?[0]
Combined Chain access person?.Address?.City

When to Use:

  • Accessing nested object properties
  • Working with arrays and collections
  • Handling events and delegates
  • Checking optional values

When Not to Use:

  • Required values (use null checks instead)
  • Performance-critical code
  • Constructor parameters
  • Simple null checks

The operators work in C# 6.0 and later versions. They're perfect for cleaning up messy null-checking code while keeping your application safe from crashes.

Basics of Null-Conditional Operators

Let's look at how null-conditional operators make your C# code cleaner and safer.

How to Write Null-Conditional Operators

C# gives you two operators to handle null checks:

Operator Purpose Example
?. Safe member access person?.Name
?[] Safe collection access array?[0]

Using the ?. Operator

Here's how ?. makes null checks simple:

// Old way (messy)
if (person != null) {
    var name = person.FirstName;
}

// New way (clean)
var name = person?.FirstName;

Using the ?[] Operator

Need to access arrays or lists safely? That's what ?[] does:

List<Person>? people = null;
Person? firstPerson = people?[0]; // No crash - just returns null

How Short-Circuiting Works

These operators are smart - they stop checking as soon as they hit null:

Expression Result if person is null Result if spouse is null
person?.Spouse?.Name null null
person?.Age null Returns actual age

Output Types

Here's what you get back when using these operators:

Scenario Output Type Example
Reference Type Same type or null string?
Value Type Nullable version int?
Method Call Nullable return type void?

See it in action:

class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}

Person? p = null;
string? name = p?.Name;    // string?
int? age = p?.Age;         // int?

These operators turn messy null-checking code into clean, readable lines. They handle the safety checks for you, so you can focus on what your code actually does.

Using the ?. Operator

The ?. operator makes null checks in C# simple and clean. Here's how it works:

Old vs New Null Checks

Let's see how ?. transforms our code:

Scenario Old Way New Way
Basic Property Access if (person != null) { name = person.Name; } name = person?.Name;
Method Call if (person != null) { person.Save(); } person?.Save();
Event Handling if (PropertyChanged != null) { PropertyChanged(this, args); } PropertyChanged?.Invoke(this, args);

Properties and Methods

The ?. operator works on both:

public class Person {
    public string FirstName { get; set; }
    public void Save() { /* save logic */ }
}

Person? p = null;
string? name = p?.FirstName;     // Returns null
p?.Save();                       // No method call

Nested Objects

Say goodbye to deep null checks:

public class Address {
    public string City { get; set; }
}

public class Person {
    public Address HomeAddress { get; set; }
}

Person? p = null;
string? city = p?.HomeAddress?.City;  // Returns null if p or HomeAddress is null

Chaining ?. Operators

Here's what happens when you chain ?. operators:

Expression What Happens When Null
person?.Spouse?.Address?.City null
order?.Customer?.Profile?.Email null
document?.Header?.Title?.Length null

Common Uses

Here's where ?. makes your code better:

// Events
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));

// Config settings
var setting = config?.AppSettings?.Settings["Key"]?.Value;

// Collections
var first = list?.FirstOrDefault()?.Name;

"The null conditional operator helps you write less code that does more." - Bill Wagner, Author of Effective C#: 50 Specific Ways to Improve Your C#

Bottom line: ?. makes your code shorter and safer. You'll write fewer if statements and catch null issues before they become problems.

Using the ?[] Operator

The ?[] operator helps you avoid null reference errors when working with arrays and collections. Here's what you need to know:

List<Person>? people = null;    
Person? firstPerson = people?[0];  // Returns null, no exception

When people is null, you get null back - no error. But heads up: if people exists and index 0 doesn't, you'll still get an IndexOutOfRange exception.

You can chain ?[] with ?. to safely access nested properties:

var thisName = people?[3]?.FirstName;  // Safe access to fourth person's name
Operation Without ?[] With ?[]
List Access if(list != null) { var item = list[0]; } var item = list?[0];
Array Item array != null ? array[5] : null array?[5]
Nested Access if(list != null && list[0] != null) { name = list[0].Name; } name = list?[0]?.Name;

For dictionaries, ?[] keeps things running when keys don't exist:

Dictionary<string, int>? scores = null;
int? score = scores?["player1"];  // Returns null safely

You can also use ?[] with LINQ:

var engineers = validCompany.Departments?[0].Employees.Count();  // Works if departments exist
var invalidCount = emptyCompany.Departments?[0].Employees.Count();  // Returns null

The performance cost? Almost zero:

Scenario Performance Impact
Normal Access Base speed
With ?[] ~1% overhead
Manual Checks ~5% overhead
Try-Catch ~20% overhead

Bottom line: Use ?[] when you expect null values - it's not meant to replace all your array access code.

Advanced Uses

Events and Thread Safety

The null-conditional operator makes event handling simpler. Here's a comparison:

Approach Code Example Thread Safety
Old Way (Unsafe) if (MyEvent != null) { MyEvent(obj1, args1); } No
Temp Variable (Safe) var tmp = MyEvent; if (tmp != null) { tmp(obj1, args1); } Yes
Null-Conditional (Safe) MyEvent?.Invoke(obj1, args1); Yes

"The '?.' operator checks its left side just once. This means it's thread-safe - perfect for raising events." - Microsoft Documentation

Working with Delegates

The null-conditional operator stops null reference errors in delegate calls:

// Event trigger with built-in null check
MaxNumberOfCoursesReached?.Invoke(this, EventArgs.Empty);

// Check return values too
if (Publish?.Invoke(severity) ?? true)
{
    // Handle what comes back
}

Combining Null Operators

Mix ?. and ?[] with other null operators to get more control:

Operator Combo What It Does Example
?. + ?? Sets fallback value person?.Name ?? "Unknown"
?[] + ?. Checks array items array?[0]?.ToString()
?. + ??= Sets null values cache?.Value ??= GetNewValue()

Using ?. with ??

These operators work great as a team:

// Handle events with a backup plan
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FirstName))) ?? DefaultAction();

// Check static variables
static string sharedString;
static int length = sharedString?.Length ?? 0;

Property Chains

The null-conditional operator shines with nested objects:

Old Way New Way
if (user != null && user.Address != null && user.Address.City != null) { city = user.Address.City; } city = user?.Address?.City;
// Check nested properties
var spouseName = person?.Spouse?.Contact?.Email;

// Mix in array checks
var departmentHead = company?.Departments?[0]?.Manager?.Name;
sbb-itb-29cd4f6

Best Practices

Here's what you need to know about using the null-conditional operator in C#:

When to Use

Scenario Example Benefit
Event handling PropertyChanged?.Invoke() Makes event raising safe
Optional chains user?.Address?.City Stops null errors
Collection access list?[0]?.Name Makes array access safe
Delegate calls callback?.Invoke() Handles delegates cleanly

When Not to Use

Situation Better Alternative Why
Required values if (value is null) throw new ArgumentNullException() Finds problems early
Simple checks if (str != null) Makes code clearer
Fast-running code Direct null check Runs faster
Constructor parameters Parameter validation Keeps data valid

Writing Clear Code

Keep it simple. Break down long chains:

// DON'T do this:
var result = data?.Users?[0]?.Profile?.Settings?.Theme?.Color ?? DefaultColor;

// DO this instead:
var user = data?.Users?[0];
var theme = user?.Profile?.Settings?.Theme;
var color = theme?.Color ?? DefaultColor;

Performance Impact

The operator adds a tiny speed cost:

// Faster way:
if (obj != null && obj.Property != null)
{
    // Work with obj.Property
}

// Cleaner but slightly slower:
var value = obj?.Property;
if (value != null)
{
    // Work with value
}

Handling Errors

Mix with null coalescing for better results:

// Set default when data's missing
var name = customer?.Name ?? "Guest";

// Throw error for missing data
var department = employee?.Department 
    ?? throw new InvalidOperationException("Employee must have department");

"The null-conditional operator isn't bad. But using it without thinking about your app's logic can hide bugs." - Colin Mackay

Remember these points:

  • Use is not null in C# 9+
  • Add ?? for defaults
  • Keep chains short (3 levels max)
  • Think twice before using in loops
  • Log errors in key spots

Common Mistakes

Let's look at the main problems developers face with null-conditional operators - and how to fix them.

Using Too Many Operators

Too many operators in one line? Your code becomes a mess. Here's what happens:

Issue Impact Fix
Long chains Hard to debug Break into smaller parts
Nested operators Performance drops Use direct null checks
Mixed with LINQ Code gets messy Split into steps

Here's what I mean:

// BAD: This is a nightmare to debug
var result = data?.Users?[0]?.Orders?.FirstOrDefault()?.Items?[0]?.Price ?? 0;

// GOOD: Now you can see what's happening
var user = data?.Users?[0];
var order = user?.Orders?.FirstOrDefault();
var price = order?.Items?[0]?.Price ?? 0;

Code Structure Issues

Want proof that null-conditional operators can slow things down? Check this out:

// Simple version: 0.1106 ns
if (!value && _state != null && curTime - _state._nextTime > 0) 
{ 
    _state._nextTime = curTime; 
}

// With null-conditional: 0.7128 ns (6.4x slower!)
if (!value && curTime - _state?._nextTime > 0) 
{ 
    _state._nextTime = curTime; 
}

Debugging Tips

| Problem | Solution | | --- | --- | --- | | Hidden nulls | Add logging at key points | | Chain breaks | Check each step separately | | Silent fails | Use debug symbols (VS2022+) |

Version Support

Version Support Action Needed
C# < 6.0 No Use if-checks
C# 6.0+ Yes Can use ?.
C# 8.0+ Yes Enable nullable reference types

Other Ways to Handle Nulls

// Option 1: Null Object Pattern
public class NullCustomer : ICustomer 
{
    public string Name => string.Empty;
}

// Option 2: Guard Clauses
public void Process(Order order)
{
    if (order is null)
        throw new ArgumentNullException(nameof(order));

    // Work with order
}

"The null-conditional operator is not a replacement for proper domain modeling. It's a tool to make code safer, not to hide design problems." - Colin Mackay

Here's the bottom line: Just because you CAN use ?. doesn't mean you SHOULD. Each null check adds overhead. For performance-critical code, stick to standard null checks.

Comparing Null-Handling Methods

Here's how C#'s null-checking methods compare:

Method Syntax Speed (ns) Best For
Classic if check if (name == null) 0.71 Simple checks
is keyword if (name is null) 0.70 Type-safe checks
Null-conditional name?.Length 0.69 Chain access
ReferenceEquals Object.ReferenceEquals(obj, null) 0.72 Memory location checks

Let me show you the difference in action:

// Old approach (messy)
if (order != null)
{
    if (order.Items != null)
    {
        var item = order.Items[0];
    }
}

// New approach (clean)
var item = order?.Items?[0];

Speed Breakdown:

  • Null-conditional: 0.69 ns
  • Classic if: 0.71 ns

The speed difference? So small it doesn't matter. Pick what's easier to read.

"The is keyword has also the big advantage that it ignores any ==/!= operator overloads on the specific class." - Thomas Claudius Huber, Author

Here's what each method can (and can't) do:

Feature Classic Check is keyword Null-conditional
Handles overloads No Yes Yes
Short-circuits Yes Yes Yes
Works with structs Yes No* Yes
Chain support No No Yes

*Except for Nullable<T>

When Can You Use Each Method?

Method Min C# Version Notes
Classic if All Works everywhere
is keyword 7.0 Modern standard
Null-conditional 6.0 Best for chains
is not null 9.0 Newest option

Bottom Line:

  • Use null-conditional (?.) for most new code
  • Stick to classic checks in speed-critical spots
  • Pick based on your C# version and team standards

Wrap-up

Here's what makes the null-conditional operator a game-changer for your code:

Feature Details
Syntax ?. for properties, ?[] for arrays
C# Version 6.0 and later
Speed 0.69 ns (slightly faster than classic checks)
Main Benefit Prevents NullReferenceException

The operator shines in these situations:

  • Multiple property checks in a chain
  • Replacing nested if statements
  • Array and collection handling
  • Event subscription management

Here's how to use it:

// Clean property chains
string city = person?.Address?.City;

// Mix with null coalescing
string name = person?.FirstName ?? "Unknown";

// Skip for complex logic
// Use if statements instead

Want to update your old code? Here's how:

1. Find Your Targets

Look for nested null checks in your codebase.

2. Make the Switch

// Old way
if (person != null)
{
    if (person.Address != null)
    {
        Console.WriteLine(person.Address.City);
    }
}

// New way
Console.WriteLine(person?.Address?.City);

3. Test Everything

Make sure your changes work as expected.

4. Add Fallbacks

Use the ?? operator where you need default values.

C#'s null handling keeps getting better:

Feature Status
Null checking Available now
Null coalescing Available now
Required properties C# 11.0
Pattern matching Enhanced in C# 9.0+

Want to stay in the loop? Check out the .NET Newsletter for daily C# updates.

FAQs

What does the null-conditional operator do in C#?

The null-conditional operator (?.) helps you write shorter, cleaner code when checking for null values in C#.

Here's what it looks like in action:

// Without null-conditional (3 lines)
if (employee != null) { 
    var name = employee.FirstName; 
}

// With null-conditional (1 line)
var name = employee?.FirstName;

You can use it in several ways:

Operation Example What It Does
Property Access person?.Name Returns null if person is null
Array Access array?[0] Returns null if array is null
Method Call obj?.Method() Skips method if obj is null
Chained Access a?.b?.c Returns null if any part is null

Want to set a default value? Combine it with the ?? operator:

// Sets "Unknown" if employees array or FirstName is null
var firstName = employees?[0]?.FirstName ?? "Unknown";

And here's something cool: The performance impact is tiny:

Operation Type Time
Standard null check 0.72 ns
Null-conditional 0.69 ns
Chain of checks 0.71 ns per item

The operator works in C# 6.0 and later versions. It stops checking as soon as it finds a null value, making your code both safer and more efficient.

Related posts

Read more