Skip to content

Commit d03ab8e

Browse files
BillWagnergewarren
andauthored
Add reference for null conditional assignment (#45637)
* Add reference for null conditional assignment Fixes #45610 Add language reference material for the null conditional assignment. * Add links from Whats New in C# Also, fix warnings. * typo * Apply suggestions from code review Co-authored-by: Genevieve Warren <[email protected]> --------- Co-authored-by: Genevieve Warren <[email protected]>
1 parent 4fa9a7a commit d03ab8e

File tree

8 files changed

+99
-24
lines changed

8 files changed

+99
-24
lines changed

docfx.json

+7-4
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",
@@ -812,8 +814,9 @@
812814
"_csharplang/proposals/field-keyword.md": "This proposal introduces a new keyword, `field`, that accesses the compiler generated backing field in a property accessor.",
813815
"_csharplang/proposals/unbound-generic-types-in-nameof.md": "This proposal introduces the ability to use unbound generic types such as `List<>` in `nameof` expressions. The type argument isn't required.",
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",
815-
"_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.",
816-
"_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.",
817+
"_csharplang/proposals/simple-lambda-parameters-with-modifiers.md": "This proposal allows lambda parameters 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.",
818+
"_csharplang/proposals/partial-events-and-constructors.md": "This proposal allows partial events and constructors to be declared in partial classes. The event and constructor can be split across class declarations.",
819+
"_csharplang/proposals/null-conditional-assignment.md": "This proposal 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

+5-4
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,6 +31,8 @@ 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

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+
3536
## ref assignment
3637

3738
*Ref assignment* `= ref` makes its left-hand operand an alias to the right-hand operand, as the following example demonstrates:
@@ -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 when the left-hand side is known to be non-null. For example, in the following code, the function `GenerateNextIndex` is called 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

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace operators;
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace operators;
24

35
public static class NullCoalescingOperator
46
{
@@ -9,6 +11,35 @@ public static void Examples()
911
NullCoalescingAssignment();
1012
}
1113

14+
public record class Human(string FirstName, string LastName)
15+
{
16+
public string FirstName { get; set; } = FirstName;
17+
}
18+
public static void AddMessageAtIndex()
19+
{
20+
List<string> messages = new List<string>(10);
21+
Human person = new Human("First", "Last");
22+
// <NullForgivingAssignment>
23+
person?.FirstName = "Scott";
24+
messages?[5] = "five";
25+
// </NullForgivingAssignment>
26+
27+
int index = 0;
28+
int[] values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
29+
30+
// <ConditionalRHS>
31+
values?[2] = GenerateNextIndex();
32+
int GenerateNextIndex() => index++;
33+
// </ConditionalRHS>
34+
35+
// <EquivalentIfStatement>
36+
if (values is not null)
37+
{
38+
values[2] = GenerateNextIndex();
39+
}
40+
// </EquivalentIfStatement>
41+
}
42+
1243
private static void WithNullConditional()
1344
{
1445
// <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)