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."
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.
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.
if (customer != null) customer.Address = newAddress;if block, for one conditional writecustomer?.Address = newAddress; was a compile error?.) and write side had inconsistent capabilitycustomer?.Address = newAddress; — one line, same meaning?. are now symmetricThe 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.
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.
var x = obj?.Prop; — null-safe since C# 6obj?.Prop = value; — null-safe since C# 14?. is evaluated exactly once.newAddress) is never evaluated — this matters if it were, say, a method call with side effects. No assignment happens. No exception is thrown. The statement is done.newAddress is evaluated normally, exactly as it would be in any ordinary assignment.customer.Address is set to the evaluated right-hand side value — identical to what customer.Address = newAddress; would have done without the ?. at all.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.
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.
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.
if would produce — there's no meaningful runtime cost difference from the defensive pattern it replaces, just less code to read and maintain.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."
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.
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.
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.
if (x != null) x.Member = value; block — this is the exact pattern the feature is meant to collapse.obj?.Prop → null if obj is null, no exception.obj?.Prop = value; → nothing happens if obj is null, no exception.if (x != null) x.Member = value; and x?.Member = value; are the same thing — one is just shorter.
?., from lesson 051) to appear as an assignment target: customer?.Address = newAddress; now compiles.if (x != null) x.Member = value; boilerplate pattern into ordinary null-conditional shorthand — the write-side counterpart to a read-side pattern you've already used since lesson 051.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?
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?
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?
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.