Skip to content

Commit be7252b

Browse files
committed
Add deconstruction lang reference
Fixes #28091 The scenario described in the referenced issue is an advanced scenario, and didn't belong in the fundamentals section. There wasn't an article on *deconstruction expressions* in the language reference. This PR adds that. In addition, there wasn't an article in the language reference on the *discard* token. This PR adds that as well.
1 parent f4a001f commit be7252b

File tree

6 files changed

+180
-0
lines changed

6 files changed

+180
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: "Deconstruction expression - extract properties or fields from a tuple or other type"
3+
description: "Learn about deconstruction expressions: expressions that extract individual properties or fields from a tuple or user defined type into discrete expressions."
4+
ms.date: 12/17/2024
5+
---
6+
# Deconstruction expression - Extract properties of fields from a tuple or other user defined type
7+
8+
A *deconstruction expression* deconstructs data fields in an instance of an object. Each discrete data element is stored in a distinct variable, as shown in the following example:
9+
10+
:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="TupleDeconstruction":::
11+
12+
The preceding code snippets creates a [tuple](../builtin-types/value-tuples.md) that has two integer values, `X` and `Y`. The second statement *deconstructs* that tuple and stores the tuple elements in discrete variables `x`, and `y`.
13+
14+
## Tuple deconstruction
15+
16+
All [tuple types](../builtin-types/value-tuples.md) support deconstruction expressions. Tuple deconstruction extracts all the tuple's elements. If you only want some of the tuple elements, use a [discard](../tokens/discard.md) for the unused tuple members, as shown in the following example:
17+
18+
:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="TupleDeconstructionWithDiscard":::
19+
20+
In the preceding example, the `Y` and `label` members are discarded. You can specify multiple discards in the same deconstruction expression.
21+
22+
## Record deconstruction
23+
24+
All [record](../builtin-types/record.md) types that have a [primary constructor](../builtin-types/record.md#positional-syntax-for-property-definition) support deconstruction. The compiler synthesizes a deconstruct method that extracts all properties declared in the primary constructor. Deconstruction for records doesn't extract properties that aren't declared using the primary constructor syntax.
25+
26+
The `record` shown in the following code declares two positional properties, `SquareFeet` and `Address`, along with another property, `RealtorNotes`:
27+
28+
:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="RecordDeconstruction":::
29+
30+
When you deconstruct a `House` object, all positional properties, and only positional properties, are deconstructed, as shown in the following example:
31+
32+
:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="RecordDeconstructionUsage":::
33+
34+
You can make use of this behavior to specify which properties of your record types are part of the compiler synthesized `Deconstruct` method.
35+
36+
## Declare `Deconstruct` methods
37+
38+
You can add deconstruct support to any class, struct, or interface you declare. You declare one or `Deconstruct` methods in your type, or as extension methods on that type. A deconstruction expression can resolve to a unique method `void Deconstruct(out var p1, ..., out var pn)`. The `Deconstruct` method can be either an instance method or an extension method. The type of each parameter in the `Deconstruct` method must match the type of the corresponding argument in the deconstruct method. The deconstruction expression assigns the value of each argument to the value of the corresponding `out` parameter in the `Deconstruct` method. If multiple `Deconstruct` methods match the deconstruction expression, the compiler reports an ambiguity.
39+
40+
The following code declares a `Point3D` struct that has two `Deconstruct` methods:
41+
42+
:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="StructDeconstruction":::
43+
44+
The first method supports deconstruction expressions that extract all three axes values: `X`, `Y`, and `Z`. The second method supports deconstructing only the planar values, `X` and `Y`. The first method has an *arity* of 3, the second has an arity of 2.
45+
46+
The preceding section described the compiler synthesized `Deconstruct` method for `record` types with a primary constructor. You can declare additional `Deconstruct` methods in record types. These can either add additional properties, remove some of the default properties, or both. You can also declare a `Deconstruct` that matches the compiler synthesized signature. If you declare such a `Deconstruct` method, the compiler won't synthesize one.
47+
48+
In most cases, multiple `Deconstruct` methods for the same type have different arities. This isn't a strict requirement. As long as the compiler can determine one unique `Deconstruct` method for a deconstruction expression, the multiple `Deconstruct` methods are allowed. However, in many cases, too many `Deconstruct` methods can lead to ambiguity errors and misleading results.
49+
50+
## C# language specification
51+
52+
For more information, see the following sections of the [C# Standard](~/_csharpstandard/standard/expressions.md#127-deconstruction):
53+
54+
## See also
55+
56+
- [C# operators and expressions](index.md)
57+
- [Tuple types](../builtin-types/value-tuples.md)
58+
- [Records](../builtin-types/record.md)
59+
- [Structure types](../builtin-types/struct.md)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection.Emit;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace operators;
9+
public class Deconstruction
10+
{
11+
public static void Examples()
12+
{
13+
// <TupleDeconstruction>
14+
var tuple = (X: 1, Y: 2);
15+
var (x, y) = tuple;
16+
17+
Console.WriteLine(x); // output: 1
18+
Console.WriteLine(y); // output: 2
19+
// </TupleDeconstruction>
20+
21+
// <TupleDeconstructionWithDiscard>
22+
var tuple2 = (X: 0, Y: 1, Label: "The origin");
23+
var (x2, _, _) = tuple2;
24+
// </TupleDeconstructionWithDiscard>
25+
26+
// <RecordDeconstructionUsage>
27+
var house = new House(1000, "123 Coder St.")
28+
{
29+
RealtorNotes = """
30+
This is a great starter home, with a separate room that's a great home office setup.
31+
"""
32+
};
33+
34+
var (squareFeet, address) = house;
35+
Console.WriteLine(squareFeet); // output: 1000
36+
Console.WriteLine(address); // output: 123 Coder St.
37+
Console.WriteLine(house.RealtorNotes);
38+
// </RecordDeconstructionUsage>
39+
40+
// <StructDeconstructionUsage>
41+
var point = new Point3D { X = 1, Y = 2, Z = 3 };
42+
43+
// Deconstruct 3D coords
44+
var (x3, y3, z3) = point;
45+
Console.WriteLine(x3); // output: 1
46+
Console.WriteLine(y3); // output: 2
47+
Console.WriteLine(z3); // output: 3
48+
49+
// Deconstruct 2D coords
50+
var (x4, y4) = point;
51+
Console.WriteLine(x4); // output: 1
52+
Console.WriteLine(y4); // output: 2
53+
// </StructDeconstructionUsage>
54+
}
55+
}
56+
57+
// <RecordDeconstruction>
58+
public record House(int SquareFeet, string Address)
59+
{
60+
public required string RealtorNotes { get; set; }
61+
}
62+
// </RecordDeconstruction>
63+
64+
// <StructDeconstruction>
65+
public struct Point3D
66+
{
67+
public int X { get; set; }
68+
public int Y { get; set; }
69+
public int Z { get; set; }
70+
71+
public void Deconstruct(out int x, out int y, out int z)
72+
{
73+
x = X;
74+
y = Y;
75+
z = Z;
76+
}
77+
78+
public void Deconstruct(out int x, out int y)
79+
{
80+
x = X;
81+
y = Y;
82+
}
83+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@
109109
IsOperator.Examples();
110110
Console.WriteLine();
111111

112+
Console.WriteLine("============= deconstruction examples ==========");
113+
Deconstruction.Examples();
114+
112115
Console.WriteLine("============ Collection expressions =================");
113116
CollectionExpressionExamples.Examples();
114117
Console.WriteLine();

docs/csharp/language-reference/toc.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,9 @@ items:
322322
- name: with expression
323323
href: ./operators/with-expression.md
324324
displayName: "records, copy"
325+
- name: Deconstruction expressions
326+
href: ./operators/deconstruction.md
327+
displayName: deconstruct, deconstruction
325328
- name: Operator overloading
326329
href: ./operators/operator-overloading.md
327330
- name: Statements
@@ -364,11 +367,15 @@ items:
364367
- name: Comments
365368
displayName: /* */, //
366369
href: ./tokens/comments.md
370+
- name: Discard
371+
displayName: discard, discard pattern
372+
href: ./tokens/discard.md
367373
- name: $ -- string interpolation
368374
href: ./tokens/interpolated.md
369375
- name: "@ -- verbatim identifier"
370376
href: ./tokens/verbatim.md
371377
- name: "\"\"\" -- raw string literal"
378+
displayName: raw string, triple quote
372379
href: ./tokens/raw-string.md
373380
- name: Attributes read by the compiler
374381
items:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
description: "A `_` is a discard, a placeholder for an unused variable in an expression"
3+
title: "Discard - _"
4+
ms.date: 12/17/2024
5+
---
6+
# Discard - A `_` acts as a placeholder for a variable
7+
8+
The `_` character services as a *discard*, which is a placeholder for an unused variable.
9+
10+
There are two uses for the *discard* token:
11+
12+
1. To declare a variable with a storage location that won't ever be read. You may need to declare a variable that you don't intend to read or otherwise use its value:
13+
- Unused `out` arguments: `var r = M(out int _, out var _, out _);`
14+
- Unused lambda expression parameters: `Action<int> _ => WriteMessage();`
15+
- Unused deconstruction arguments: `(int _, var answer) = M();`
16+
1. In a [discard pattern](../operators/patterns.md#discard-pattern) to match any expression. You'll usually add a `_` pattern to satisfy exhaustiveness requirements.
17+
18+
The `_` token is a valid identifier in C#. The `_` token is interpreted as a discard only when no valid identifier named `_` is found in scope.
19+
20+
A discard can't be read as a variable. The compiler reports an error if your code reads a discard. The compiler can avoid allocating the storage for a discard in some situations where that is safe.
21+
22+
## See also
23+
24+
- [Tuples](../builtin-types/value-tuples.md)
25+
- [Deconstruction](../tokens/discard.md)
26+
- [Discard pattern](../operators/patterns.md#discard-pattern)

docs/csharp/language-reference/tokens/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@ Special characters are predefined, contextual characters that modify the program
2020

2121
- [@](./verbatim.md), the verbatim identifier character.
2222
- [$](./interpolated.md), the interpolated string character.
23+
- ["""](./raw-string.md), A sequence of three or more `"` characters provides the delimiters for a raw string literal.
24+
- [_](./discard.md), a `_` represents a *discard*, a placeholder for an unused variable.
2325

2426
This section only includes those tokens that are not operators. See the [operators](../operators/index.md) section for all operators.

0 commit comments

Comments
 (0)