Skip to content

Commit f253648

Browse files
committed
Add reference for null conditional assignment
Fixes dotnet#45610 Add language reference material for the null conditional assignment.
1 parent 230b648 commit f253648

File tree

7 files changed

+65
-21
lines changed

7 files changed

+65
-21
lines changed

docfx.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
"unbound-generic-types-in-nameof.md",
5959
"first-class-span-types.md",
6060
"simple-lambda-parameters-with-modifiers.md",
61-
"partial-events-and-constructors.md"
61+
"partial-events-and-constructors.md",
62+
"null-conditional-assignment.md"
6263
],
6364
"src": "_csharplang/proposals",
6465
"dest": "csharp/language-reference/proposals",
@@ -509,7 +510,7 @@
509510
"_csharplang/proposals/csharp-11.0/*.md": "09/30/2022",
510511
"_csharplang/proposals/csharp-12.0/*.md": "08/15/2023",
511512
"_csharplang/proposals/csharp-13.0/*.md": "10/31/2024",
512-
"_csharplang/proposals/*.md": "03/11/2025",
513+
"_csharplang/proposals/*.md": "04/04/2025",
513514
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "11/08/2022",
514515
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "11/08/2023",
515516
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "11/09/2024",
@@ -689,6 +690,7 @@
689690
"_csharplang/proposals/first-class-span-types.md": "First-class span types",
690691
"_csharplang/proposals/simple-lambda-parameters-with-modifiers.md": "Simple lambda parameters with modifiers",
691692
"_csharplang/proposals/partial-events-and-constructors.md": "Partial events and constructors",
693+
"_csharplang/proposals/null-conditional-assignment.md": "Null conditional assignment",
692694
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "C# compiler breaking changes since C# 10",
693695
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "C# compiler breaking changes since C# 11",
694696
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "C# compiler breaking changes since C# 12",
@@ -814,6 +816,7 @@
814816
"_csharplang/proposals/first-class-span-types.md": "This proposal provides several implicit conversions to `Span<T>` and `ReadOnlySpan<T>` that enable library authors to have fewer overloads and developers to write code that resolves to faster Span based APIs",
815817
"_csharplang/proposals/simple-lambda-parameters-with-modifiers.md": "This proposal provides allows lambda parmaeters to be declared with modifiers without requiring their type names. You can add modifiers like `ref` and `out` to lambda parameters without specifying their type.",
816818
"_csharplang/proposals/partial-events-and-constructors.md": "This proposal provides allows partial events and constructors to be declared in partial classes. This allows the event and constructor to be split across class declarations.",
819+
"_csharplang/proposals/null-conditional-assignment.md": "This proposal provides allows the null conditional operator to be used for the destination of assignment expressions. This allows you to assign a value to a property or field only if the left side is not null.",
817820
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "Learn about any breaking changes since the initial release of C# 10 and included in C# 11",
818821
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "Learn about any breaking changes since the initial release of C# 11 and included in C# 12",
819822
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "Learn about any breaking changes since the initial release of C# 12 and included in C# 13",

docs/csharp/language-reference/operators/assignment-operator.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
---
22
title: "Assignment operators - assign an expression to a variable"
33
description: "C# Assignment sets the value of the expression. Alternatively, `ref` assignment sets the reference of a reference variable."
4-
ms.date: 11/21/2024
4+
ms.date: 04/04/2025
55
f1_keywords:
66
- "=_CSharpKeyword"
77
helpviewer_keywords:
88
- "= operator [C#]"
9-
ms.assetid: d802a6d5-32f0-42b8-b180-12f5a081bfc1
109
---
1110
# Assignment operators (C# reference)
1211

@@ -32,7 +31,9 @@ The left-hand operand of an assignment receives the *value* of the right-hand op
3231

3332
This operation is called *value assignment*: the value is assigned.
3433

35-
## ref assignment
34+
Beginning with C# 14, the left hand side of a value assignment can include a [null conditional member expression](./member-access-operators.md#null-conditional-operators--and-), such as `?.` or `?[]`. If the left hand side is null, the right hand side expression isn't evaluated.
35+
36+
## Reference assignment
3637

3738
*Ref assignment* `= ref` makes its left-hand operand an alias to the right-hand operand, as the following example demonstrates:
3839

@@ -66,11 +67,11 @@ x = x op y
6667

6768
Except that `x` is only evaluated once.
6869

69-
The [arithmetic](arithmetic-operators.md#compound-assignment), [Boolean logical](boolean-logical-operators.md#compound-assignment), and [bitwise logical and shift](bitwise-and-shift-operators.md#compound-assignment) operators all support compount assignment.
70+
The [arithmetic](arithmetic-operators.md#compound-assignment), [Boolean logical](boolean-logical-operators.md#compound-assignment), and [bitwise logical and shift](bitwise-and-shift-operators.md#compound-assignment) operators all support compound assignment.
7071

7172
## Null-coalescing assignment
7273

73-
You can use the null-coalescing assignment operator `??=` to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to `null`. For more information, see the [?? and ??= operators](null-coalescing-operator.md) article.
74+
You can use the null-coalescing assignment operator `??=` to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to `null`. For more information, see the [`??` and `??=` operators](null-coalescing-operator.md) article.
7475

7576
## Operator overloadability
7677

docs/csharp/language-reference/operators/member-access-operators.md

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Member access and null-conditional operators and expressions:"
33
description: "C# operators that you use to access type members or null-conditionally access type members. These operators include the dot operator - `.`, indexers - `[`, `]`, `^` and `..`, and invocation - `(`, `)`."
4-
ms.date: 07/31/2024
4+
ms.date: 04/04/2025
55
author: pkulikov
66
f1_keywords:
77
- "._CSharpKeyword"
@@ -35,7 +35,7 @@ helpviewer_keywords:
3535
---
3636
# Member access operators and expressions - the dot, indexer, and invocation operators.
3737

38-
You use several operators and expressions to access a type member. These operators include member access (`.`), array element or indexer access (`[]`), index-from-end (`^`), range (`..`), null-conditional operators (`?.` and `?[]`), and method invocation (`()`). These include the *null-conditional* member access (`?.`), and indexer access (`?[]`) operators.
38+
You use several operators and expressions to access a type member. Member access operators include member access (`.`), array element, or indexer access (`[]`), index-from-end (`^`), range (`..`), null-conditional operators (`?.` and `?[]`), and method invocation (`()`). These include the *null-conditional* member access (`?.`), and indexer access (`?[]`) operators.
3939

4040
- [`.` (member access)](#member-access-expression-): to access a member of a namespace or a type
4141
- [`[]` (array element or indexer access)](#indexer-operator-): to access an array element or a type indexer
@@ -138,7 +138,7 @@ The following examples demonstrate the usage of the `?.` and `?[]` operators:
138138
:::code language="csharp" source="snippets/shared/MemberAccessOperators.cs" id="SnippetNullConditional" interactive="try-dotnet-method":::
139139
:::code language="csharp" source="snippets/shared/MemberAccessOperators2.cs" interactive="try-dotnet":::
140140

141-
The first of the preceding two examples also uses the [null-coalescing operator `??`](null-coalescing-operator.md) to specify an alternative expression to evaluate in case the result of a null-conditional operation is `null`.
141+
The first preceding example also uses the [null-coalescing operator `??`](null-coalescing-operator.md) to specify an alternative expression to evaluate in case the result of a null-conditional operation is `null`.
142142

143143
If `a.x` or `a[x]` is of a non-nullable value type `T`, `a?.x` or `a?[x]` is of the corresponding [nullable value type](../builtin-types/nullable-value-types.md) `T?`. If you need an expression of type `T`, apply the null-coalescing operator `??` to a null-conditional expression, as the following example shows:
144144

@@ -147,9 +147,23 @@ If `a.x` or `a[x]` is of a non-nullable value type `T`, `a?.x` or `a?[x]` is of
147147
In the preceding example, if you don't use the `??` operator, `numbers?.Length < 2` evaluates to `false` when `numbers` is `null`.
148148

149149
> [!NOTE]
150-
> The `?.` operator evaluates its left-hand operand no more than once, guaranteeing that it cannot be changed to `null` after being verified as non-null.
150+
> The `?.` operator evaluates its left-hand operand no more than once, guaranteeing that it can't be changed to `null` after being verified as non-null.
151151
152-
The null-conditional member access operator `?.` is also known as the Elvis operator.
152+
Beginning in C# 14, assignment is permissible with a null conditional access expression (`?.` and `?[]`) on reference types. For example, see the following method:
153+
154+
:::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="NullForgivingAssignment":::
155+
156+
The preceding example shows assignment to a property and an indexed element on a reference type that might be null. An important behavior for this assignment is that the expression on the right hand side of the `=` is evaluated only once the left hand side is known to be non-null. For example, in the following code, the function `GenerateNextIndex` is call only when the `values` array isn't null. If the `values` array is null, `GenerateNextIndex` isn't called:
157+
158+
:::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="NullForgivingAssignment":::
159+
160+
In other words, the preceding code is equivalent to the following code using an `if` statement for the null check:
161+
162+
:::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="EquivalentIfStatement":::
163+
164+
In addition to assignment, any form of [compound assignment](./assignment-operator.md#compound-assignment), such as `+=` or `-=` are allowed. However, increment (`++`) and decrement (`--`) aren't allowed.
165+
166+
This enhancement doesn't classify a null conditional expression as a variable. It can't be `ref` assigned, nor can it be assigned to a `ref` variable, or passed to a method as a `ref` or `out` argument.
153167

154168
### Thread-safe delegate invocation
155169

@@ -175,7 +189,7 @@ The preceding example is a thread-safe way to ensure that only a non-null `handl
175189

176190
Use parentheses, `()`, to call a [method](../../programming-guide/classes-and-structs/methods.md) or invoke a [delegate](../../programming-guide/delegates/index.md).
177191

178-
The following example demonstrates how to call a method, with or without arguments, and invoke a delegate:
192+
The following code demonstrates how to call a method, with or without arguments, and invoke a delegate:
179193

180194
:::code language="csharp" source="snippets/shared/MemberAccessOperators.cs" id="Invocation" interactive="try-dotnet-method":::
181195

docs/csharp/language-reference/operators/null-coalescing-operator.md

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "?? and ??= operators - null-coalescing operators"
33
description: "The `??` and `??=` operators are the C# null-coalescing operators. They return the value of the left-hand operand if it isn't null. Otherwise, they return the value of the right-hand operand"
4-
ms.date: 11/28/2022
4+
ms.date: 04/04/2025
55
f1_keywords:
66
- "??_CSharpKeyword"
77
- "??=_CSharpKeyword"
@@ -10,7 +10,6 @@ helpviewer_keywords:
1010
- "?? operator [C#]"
1111
- "null-coalescing assignment [C#]"
1212
- "??= operator [C#]"
13-
ms.assetid: 088b1f0d-c1af-4fe1-b4b8-196fd5ea9132
1413
---
1514
# ?? and ??= operators - the null-coalescing operators
1615

@@ -20,13 +19,13 @@ ms.assetid: 088b1f0d-c1af-4fe1-b4b8-196fd5ea9132
2019

2120
The null-coalescing operator `??` returns the value of its left-hand operand if it isn't `null`; otherwise, it evaluates the right-hand operand and returns its result. The `??` operator doesn't evaluate its right-hand operand if the left-hand operand evaluates to non-null. The null-coalescing assignment operator `??=` assigns the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to `null`. The `??=` operator doesn't evaluate its right-hand operand if the left-hand operand evaluates to non-null.
2221

23-
[!code-csharp[null-coalescing assignment](snippets/shared/NullCoalescingOperator.cs#Assignment)]
22+
:::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="Assignment":::
2423

2524
The left-hand operand of the `??=` operator must be a variable, a [property](../../programming-guide/classes-and-structs/properties.md), or an [indexer](../../programming-guide/indexers/index.md) element.
2625

2726
The type of the left-hand operand of the `??` and `??=` operators can't be a non-nullable value type. In particular, you can use the null-coalescing operators with unconstrained type parameters:
2827

29-
[!code-csharp[unconstrained type parameter](snippets/shared/NullCoalescingOperator.cs#UnconstrainedType)]
28+
:::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="UnconstrainedType":::
3029

3130
The null-coalescing operators are right-associative. That is, expressions of the form
3231

@@ -48,17 +47,17 @@ The `??` and `??=` operators can be useful in the following scenarios:
4847

4948
- In expressions with the [null-conditional operators `?.` and `?[]`](member-access-operators.md#null-conditional-operators--and-), you can use the `??` operator to provide an alternative expression to evaluate in case the result of the expression with null-conditional operations is `null`:
5049

51-
[!code-csharp-interactive[with null-conditional](snippets/shared/NullCoalescingOperator.cs#WithNullConditional)]
50+
:::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="WithNullConditional" interactive="try-dotnet-method":::
5251

5352
- When you work with [nullable value types](../builtin-types/nullable-value-types.md) and need to provide a value of an underlying value type, use the `??` operator to specify the value to provide in case a nullable type value is `null`:
5453

55-
[!code-csharp-interactive[with nullable types](snippets/shared/NullCoalescingOperator.cs#WithNullableTypes)]
54+
:::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="WithNullableTypes" interactive="try-dotnet-method":::
5655

5756
Use the <xref:System.Nullable%601.GetValueOrDefault?displayProperty=nameWithType> method if the value to be used when a nullable type value is `null` should be the default value of the underlying value type.
5857

5958
- You can use a [`throw` expression](../statements/exception-handling-statements.md#the-throw-expression) as the right-hand operand of the `??` operator to make the argument-checking code more concise:
6059

61-
[!code-csharp[with throw expression](snippets/shared/NullCoalescingOperator.cs#WithThrowExpression)]
60+
:::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="WithThrowExpression" interactive="try-dotnet-method":::
6261

6362
The preceding example also demonstrates how to use [expression-bodied members](../../programming-guide/statements-expressions-operators/expression-bodied-members.md) to define a property.
6463

docs/csharp/language-reference/operators/snippets/shared/NullCoalescingOperator.cs

+26
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,32 @@ public static void Examples()
99
NullCoalescingAssignment();
1010
}
1111

12+
public record class Person(string FirstName, string LastName);
13+
public static void AddMessageAtIndex()
14+
{
15+
List<string> messages = new List<string>(10);
16+
Person person = new Person("First", "Last");
17+
// <NullForgivingAssignment>
18+
person?.FirstName = "Scott";
19+
messages?[5] = "five";
20+
// </NullForgivingAssignment>
21+
22+
int index = 0;
23+
int[] values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
24+
25+
// <ConditionalRHS>
26+
values?[2] = GenerateNextIndex();
27+
int GenerateNextIndex() => index++;
28+
// </ConditionalRHS>
29+
30+
// <EquivalentIfStatement>
31+
if (values is not null)
32+
{
33+
values[2] = GenerateNextIndex();
34+
}
35+
// </EquivalentIfStatement>
36+
}
37+
1238
private static void WithNullConditional()
1339
{
1440
// <SnippetWithNullConditional>

docs/csharp/language-reference/operators/snippets/shared/operators.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
<Nullable>enable</Nullable>
77
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
88
<ImplicitUsings>true</ImplicitUsings>
9-
<NoWarn>7022</NoWarn>
109
<LangVersion>preview</LangVersion>
1110
</PropertyGroup>
1211

docs/csharp/specification/toc.yml

+2
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ items:
175175
href: ../../../_csharplang/proposals/unbound-generic-types-in-nameof.md
176176
- name: Overload resolution priority
177177
href: ../../../_csharplang/proposals/csharp-13.0/overload-resolution-priority.md
178+
- name: Null conditional assignment
179+
href: ../../../_csharplang/proposals/null-conditional-assignment.md
178180
- name: Statements
179181
items:
180182
- name: Global using directive

0 commit comments

Comments
 (0)