← Open in the full interactive course (progress tracking, search & more)

The null-conditional operator (lesson 051) has always been able to answer "what's in there, if anything." C# 14 finally lets it also answer "put this in there, if there's anywhere to put it."

Lesson 051 taught you ?. as the null-safe way to read a member: var city = customer?.Address?.City; quietly evaluates to null the instant any link in that chain is null, instead of throwing a NullReferenceException. It's one of the most useful pieces of syntax in the language — but only ever on the read side. Try to use it on the write side, as an assignment target, and before C# 14 that was a flat compile error: customer?.Address = newAddress; simply wasn't legal C#. You had to fall back to the exact defensive if block the operator exists to eliminate everywhere else.

In this lesson, you'll learn that C# 14 lifts that restriction: ?. can now appear on the left side of an assignment, meaning "assign this — but only if the thing on the left isn't null; otherwise, skip the assignment entirely and do nothing."

What Is It?

The Simple Explanation

Null-conditional assignment lets you write customer?.Address = newAddress; directly. If customer is not null, the assignment happens exactly as you'd expect. If customer is null, the entire right-hand side is never evaluated, no assignment happens, and — critically — nothing throws. Execution simply moves on to the next statement.

The Technical Definition

C# 14 extends the null-conditional operator (?. and, correspondingly, ?[...] for indexers) to be legal as the target of a simple assignment expression. When the receiver evaluates to null, the entire assignment expression short-circuits: the right-hand side is never evaluated at all — an important detail if that expression has side effects — and the statement completes with no exception and no assignment performed. When the receiver is non-null, the assignment proceeds exactly as an ordinary, unconditional assignment would.

Before C# 14

With C# 14

Why Does It Exist?

The Problem — An Asymmetric Operator

The null-conditional operator's whole reason for existing, since lesson 051, has been to collapse "check for null, then act" into one expression — for reads. But the exact same pattern on a write was left completely unaddressed: a conditional assignment still required the full, explicit defensive-if boilerplate the operator was invented to eliminate everywhere else. Developers routinely reached for ?. out of habit when writing to a possibly-null reference, hit a compile error, and had to unwind back to the older, more verbose pattern — an inconsistency that stood out precisely because the operator worked so well everywhere else.

The Solution — Make the Operator Symmetric

C# 14 closes the gap by allowing the exact same ?. syntax to appear on the left of an assignment, with the exact same "null means skip, don't throw" semantics you already know from reads. Nothing new needs to be learned conceptually — it's the same operator, doing the same kind of short-circuiting, in the one remaining place it hadn't been allowed to.

Big Picture

Read Side (Lesson 051)
var x = obj?.Prop; — null-safe since C# 6
Write Side (C# 14)
obj?.Prop = value; — null-safe since C# 14
Null Receiver
Right-hand side never evaluated; nothing throws; nothing happens
Non-Null Receiver
Behaves exactly like an ordinary, unconditional assignment

How It Works

EVALUATING customer?.Address = newAddress;
1. EVALUATE THE RECEIVER — customer
2. IS IT NULL?
3. IF NOT NULL — EVALUATE THE RIGHT-HAND SIDE
4. PERFORM THE ASSIGNMENT

Simple Example

public class Address { public string City { get; set; } = ""; } public class Customer { public Address? Address { get; set; } } Customer? customer = FindCustomer(id); // might return null // Before C# 14 — the defensive if block: if (customer != null) { customer.Address = new Address { City = "Seattle" }; } // C# 14 — the exact same behavior, one line: customer?.Address = new Address { City = "Seattle" }; // If customer is null here, nothing happens — no exception, no assignment, // and new Address { City = "Seattle" } is never even constructed.

Code → Meaning → Result: Both blocks behave identically at runtime. The C# 14 version simply says the same thing in one expression instead of three lines, and — notably — skips constructing the right-hand side entirely when customer is null, since that expression is never reached.

Real-World Example

Consider an event handler pattern you've likely written many times: an optional callback field that a caller may or may not have set. OnStatusChanged?.Invoke(newStatus); is the classic null-conditional read-side pattern for safely raising an event that might have no subscribers. Null-conditional assignment now gives you the same safety on the configuration side: a UI component with an optional, injectable logger — _diagnostics?.LastError = ex.Message; — records the error only if a diagnostics object was actually supplied, without a wrapping if block cluttering what is, conceptually, a single, simple "record this if there's somewhere to record it" operation. Multiply that pattern across dozens of optional dependencies in a large application, and the reduction in defensive-null-check boilerplate adds up.

Analogy

Dropping a Letter Through a Mail Slot That Might Not Exist

Reading with ?. is like checking a mailbox that might not be there: if it exists, you take out the mail; if it doesn't, you walk away empty-handed, without incident. Null-conditional assignment is the exact same idea run in reverse — you're trying to drop a letter into a mailbox that might not exist. If the mailbox is there, the letter goes in, exactly as normal. If it isn't, you simply don't drop the letter — you don't stand there confused, and you certainly don't set off an alarm. The letter you were holding (the right-hand side) doesn't even need to be written yet if there's no mailbox to receive it, which is exactly why the right-hand side is never evaluated when the receiver is null.

Under the Hood

Common Confusion

1. "The right-hand side always runs, only the assignment is skipped" — no, the whole thing is skipped

It's tempting to assume the value being assigned is always computed and only the final "store it" step is conditional. That's incorrect: when the receiver is null, C# never evaluates the right-hand side at all. If that expression calls a method with a side effect — logging, incrementing a counter, mutating something else — that side effect simply doesn't happen when the receiver is null. This is exactly consistent with how ?. has always short-circuited on the read side, but it's worth stating explicitly, since an assignment's right side is easy to assume "always runs."

2. "This throws if the receiver is null, just like a plain assignment would" — no, it's specifically designed not to

An ordinary customer.Address = newAddress; throws a NullReferenceException the instant customer is null. The entire point of null-conditional assignment is the opposite guarantee: a null receiver means the statement quietly does nothing and execution simply continues — no exception at all. If you actually want an exception when the receiver turns out to be null, this is the wrong tool; keep the ordinary, unconditional assignment for that case.

Common Mistakes

Mistake 1 — Using it where a null receiver should actually be an error

Reaching for customer?.Address = newAddress; in a code path where customer being null genuinely represents a bug — silently swallowing what should have been a loud, early failure, and letting the program continue in a state its author never intended.

Use null-conditional assignment specifically where "there's genuinely nothing to do" is a legitimate, expected outcome — an optional dependency, an event with no subscribers. Where a null value is actually invalid, keep an explicit check (or lesson 041's exception-throwing patterns) so the bug surfaces immediately instead of being silently absorbed.

Mistake 2 — Assuming the right-hand side always executes

Writing logger?.LastMessage = ComputeExpensiveDiagnostic(); and assuming ComputeExpensiveDiagnostic() always runs regardless of whether logger is null — then being surprised when a side effect inside it (like a counter increment) doesn't happen in some code paths.

Remember that the right-hand side is skipped entirely when the receiver is null. If a right-hand-side expression's side effects must always happen regardless of the receiver, compute it in a separate statement first, then assign it unconditionally.

When Should I Use It?

Mental Model

Read side (lesson 051): obj?.Prop → null if obj is null, no exception.
Write side (C# 14): obj?.Prop = value; → nothing happens if obj is null, no exception.
Either way: null means "skip," not "crash" — and on the write side, the value being assigned isn't even computed when skipped.

Remember: if (x != null) x.Member = value; and x?.Member = value; are the same thing — one is just shorter.

Key Takeaway


Check Your Understanding

Let's confirm the short-circuiting behavior — including the right-hand-side subtlety — landed clearly.

1. What happens when customer?.Address = newAddress; executes and customer is null?

Show answer

Correct: B

Why B is correct: This is the entire point of the feature — a null receiver means the assignment is skipped safely, mirroring the read-side ?. behavior from lesson 051 rather than throwing.

Why A is incorrect: Throwing on a null receiver is exactly the ordinary-assignment behavior this feature exists to avoid; the whole motivation was skipping the exception.

Why C is incorrect: Null-conditional assignment never creates or substitutes a new instance for a null receiver — it simply does nothing when the receiver is null.

Why D is incorrect: This is exactly the syntax C# 14 makes legal — it compiles cleanly, which is the entire subject of this lesson.

Reinforcement: Null-conditional assignment always means "null receiver → skip, don't throw."

2. Given logger?.LastMessage = BuildMessage(); where BuildMessage() has an observable side effect (it increments a counter), and logger is null at runtime — does BuildMessage() get called?

Show answer

Correct: B

Why B is correct: As "Common Confusion" #1 and "Under the Hood" both explain, the short-circuit happens before the right-hand side is evaluated at all — a null receiver means BuildMessage() is never called, and its side effect never occurs.

Why A is incorrect: This is precisely the misconception the lesson calls out — evaluation, not just assignment, is skipped when the receiver is null.

Why C is incorrect: The method call itself doesn't happen at all in this case — there's no return value to discard, because it's never invoked.

Why D is incorrect: Whether a method is asynchronous has no bearing on this short-circuiting rule — the behavior is the same either way.

Reinforcement: A null receiver skips the entire right-hand side expression, not just the final store.

3. Which existing C# pattern does null-conditional assignment most directly replace, according to this lesson?

Show answer

Correct: B

Why B is correct: This is the exact defensive boilerplate pattern the "Why Does It Exist?" and "Simple Example" sections both identify as what null-conditional assignment collapses into one line.

Why A is incorrect: The null-coalescing assignment operator (??=) assigns a fallback to a possibly-null variable itself — a different operator solving a different problem, not this feature.

Why C is incorrect: The null-forgiving operator (!) tells the compiler to suppress a nullable warning while still risking a real runtime exception if the value actually is null — the opposite of null-conditional assignment's safe skip.

Why D is incorrect: Wrapping an assignment in a try/catch to suppress a NullReferenceException is a heavier, less idiomatic pattern than the simple if check this feature actually targets.

Reinforcement: Null-conditional assignment is a direct, one-line replacement for the single-statement defensive-if pattern.

The null-conditional operator you learned in lesson 051 is now fully symmetric — safe on reads and safe on writes. Lesson 327 continues the Part XII tour with enhanced span conversions, and more C# 14 additions follow right after.


dotnetmadeeasy.com — Learn C# and .NET, the right way.