From ffb3a5c07775a5f07df4ba846bbe7b2e7c79696c Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 20 Dec 2024 16:52:37 -0500 Subject: [PATCH] Add deconstruction lang reference (#44023) * 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. * fix build issue * proofread and edit * Update docs/csharp/language-reference/tokens/discard.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- .../operators/deconstruction.md | 61 +++++++++++++ .../snippets/shared/Deconstruction.cs | 88 +++++++++++++++++++ .../operators/snippets/shared/Program.cs | 3 + docs/csharp/language-reference/toc.yml | 7 ++ .../language-reference/tokens/discard.md | 26 ++++++ .../csharp/language-reference/tokens/index.md | 6 +- 6 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 docs/csharp/language-reference/operators/deconstruction.md create mode 100644 docs/csharp/language-reference/operators/snippets/shared/Deconstruction.cs create mode 100644 docs/csharp/language-reference/tokens/discard.md diff --git a/docs/csharp/language-reference/operators/deconstruction.md b/docs/csharp/language-reference/operators/deconstruction.md new file mode 100644 index 0000000000000..de77c4f3c86aa --- /dev/null +++ b/docs/csharp/language-reference/operators/deconstruction.md @@ -0,0 +1,61 @@ +--- +title: "Deconstruction expression - extract properties or fields from a tuple or other type" +description: "Learn about deconstruction expressions: expressions that extract individual properties or fields from a tuple or user defined type into discrete expressions." +ms.date: 12/17/2024 +--- +# Deconstruction expression - Extract properties of fields from a tuple or other user-defined type + +A *deconstruction expression* extracts data fields from an instance of an object. Each discrete data element is written to a distinct variable, as shown in the following example: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="TupleDeconstruction"::: + +The preceding code snippet 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`. + +## Tuple deconstruction + +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: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="TupleDeconstructionWithDiscard"::: + +In the preceding example, the `Y` and `label` members are discarded. You can specify multiple discards in the same deconstruction expression. You can use discards for all the members of the tuple. The following example is legal, although not useful: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="AllDiscards"::: + +## Record deconstruction + +[Record](../builtin-types/record.md) types that have a [primary constructor](../builtin-types/record.md#positional-syntax-for-property-definition) support deconstruction for positional parameters. The compiler synthesizes a `Deconstruct` method that extracts the properties synthesized from positional parameters in the primary constructor. The compiler-synthesized `Deconstruction` method doesn't extract properties declared as properties in the record type. + +The `record` shown in the following code declares two positional properties, `SquareFeet` and `Address`, along with another property, `RealtorNotes`: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="RecordDeconstruction"::: + +When you deconstruct a `House` object, all positional properties, and only positional properties, are deconstructed, as shown in the following example: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="RecordDeconstructionUsage"::: + +You can make use of this behavior to specify which properties of your record types are part of the compiler-synthesized `Deconstruct` method. + +## Declare `Deconstruct` methods + +You can add deconstruction 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 calls a 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 deconstruction expression. 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 error for the ambiguity. + +The following code declares a `Point3D` struct that has two `Deconstruct` methods: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="StructDeconstruction"::: + +The first method supports deconstruction expressions that extract all three axis 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. + +The preceding section described the compiler-synthesized `Deconstruct` method for `record` types with a primary constructor. You can declare more `Deconstruct` methods in record types. These methods can either add other 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 doesn't synthesize one. + +Multiple `Deconstruct` methods are allowed as long as the compiler can determine one unique `Deconstruct` method for a deconstruction expression. Typically, multiple `Deconstruct` methods for the same type have different numbers of parameters. You can also create multiple `Deconstruct` methods that differ by parameter types. However, in many cases, too many `Deconstruct` methods can lead to ambiguity errors and misleading results. + +## C# language specification + +For more information, see the deconstruction section of the [C# Standard](~/_csharpstandard/standard/expressions.md#127-deconstruction). + +## See also + +- [C# operators and expressions](index.md) +- [Tuple types](../builtin-types/value-tuples.md) +- [Records](../builtin-types/record.md) +- [Structure types](../builtin-types/struct.md) diff --git a/docs/csharp/language-reference/operators/snippets/shared/Deconstruction.cs b/docs/csharp/language-reference/operators/snippets/shared/Deconstruction.cs new file mode 100644 index 0000000000000..581cadebe3d5f --- /dev/null +++ b/docs/csharp/language-reference/operators/snippets/shared/Deconstruction.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace operators; +public class Deconstruction +{ + public static void Examples() + { + // + var tuple = (X: 1, Y: 2); + var (x, y) = tuple; + + Console.WriteLine(x); // output: 1 + Console.WriteLine(y); // output: 2 + // + + // + var tuple2 = (X: 0, Y: 1, Label: "The origin"); + var (x2, _, _) = tuple2; + // + + // + var (_, _, _) = tuple2; + // + + // + var house = new House(1000, "123 Coder St.") + { + RealtorNotes = """ + This is a great starter home, with a separate room that's a great home office setup. + """ + }; + + var (squareFeet, address) = house; + Console.WriteLine(squareFeet); // output: 1000 + Console.WriteLine(address); // output: 123 Coder St. + Console.WriteLine(house.RealtorNotes); + // + + // + var point = new Point3D { X = 1, Y = 2, Z = 3 }; + + // Deconstruct 3D coords + var (x3, y3, z3) = point; + Console.WriteLine(x3); // output: 1 + Console.WriteLine(y3); // output: 2 + Console.WriteLine(z3); // output: 3 + + // Deconstruct 2D coords + var (x4, y4) = point; + Console.WriteLine(x4); // output: 1 + Console.WriteLine(y4); // output: 2 + // + } +} + +// +public record House(int SquareFeet, string Address) +{ + public required string RealtorNotes { get; set; } +} +// + +// +public struct Point3D +{ + public int X { get; set; } + public int Y { get; set; } + public int Z { get; set; } + + public void Deconstruct(out int x, out int y, out int z) + { + x = X; + y = Y; + z = Z; + } + + public void Deconstruct(out int x, out int y) + { + x = X; + y = Y; + } +} +// diff --git a/docs/csharp/language-reference/operators/snippets/shared/Program.cs b/docs/csharp/language-reference/operators/snippets/shared/Program.cs index 504183d61649a..5a5a7383d66c4 100644 --- a/docs/csharp/language-reference/operators/snippets/shared/Program.cs +++ b/docs/csharp/language-reference/operators/snippets/shared/Program.cs @@ -109,6 +109,9 @@ IsOperator.Examples(); Console.WriteLine(); +Console.WriteLine("============= deconstruction examples =========="); +Deconstruction.Examples(); + Console.WriteLine("============ Collection expressions ================="); CollectionExpressionExamples.Examples(); Console.WriteLine(); diff --git a/docs/csharp/language-reference/toc.yml b/docs/csharp/language-reference/toc.yml index 5de77035ebd85..1e4ef81c0dec8 100644 --- a/docs/csharp/language-reference/toc.yml +++ b/docs/csharp/language-reference/toc.yml @@ -322,6 +322,9 @@ items: - name: with expression href: ./operators/with-expression.md displayName: "records, copy" + - name: Deconstruction expression + href: ./operators/deconstruction.md + displayName: deconstruct, deconstruction - name: Operator overloading href: ./operators/operator-overloading.md - name: Statements @@ -364,11 +367,15 @@ items: - name: Comments displayName: /* */, // href: ./tokens/comments.md + - name: Discard + displayName: discard, discard pattern + href: ./tokens/discard.md - name: $ -- string interpolation href: ./tokens/interpolated.md - name: "@ -- verbatim identifier" href: ./tokens/verbatim.md - name: "\"\"\" -- raw string literal" + displayName: raw string, triple quote href: ./tokens/raw-string.md - name: Attributes read by the compiler items: diff --git a/docs/csharp/language-reference/tokens/discard.md b/docs/csharp/language-reference/tokens/discard.md new file mode 100644 index 0000000000000..0755f2811771d --- /dev/null +++ b/docs/csharp/language-reference/tokens/discard.md @@ -0,0 +1,26 @@ +--- +description: "A `_` is a discard, a placeholder for an unused variable in an expression" +title: "Discard - _" +ms.date: 12/17/2024 +--- +# Discard - A `_` acts as a placeholder for a variable + +The `_` character serves as a *discard*, which is a placeholder for an unused variable. + +There are two uses for the *discard* token: + +1. To declare an unused variable. A discard can't be read or accessed. + - Unused `out` arguments: `var r = M(out int _, out var _, out _);` + - Unused lambda expression parameters: `Action _ => WriteMessage();` + - Unused deconstruction arguments: `(int _, var answer) = M();` +1. To match any expression in a [discard pattern](../operators/patterns.md#discard-pattern). You can add a `_` pattern to satisfy exhaustiveness requirements. + +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. + +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. + +## See also + +- [Tuples](../builtin-types/value-tuples.md) +- [Deconstruction](../tokens/discard.md) +- [Discard pattern](../operators/patterns.md#discard-pattern) diff --git a/docs/csharp/language-reference/tokens/index.md b/docs/csharp/language-reference/tokens/index.md index f85dc15a5abc4..5592c29f8419d 100644 --- a/docs/csharp/language-reference/tokens/index.md +++ b/docs/csharp/language-reference/tokens/index.md @@ -16,9 +16,11 @@ ms.assetid: 4c5c0539-2e37-40b7-91ce-75af5aabd3f9 # C# Special Characters -Special characters are predefined, contextual characters that modify the program element (a literal string, an identifier, or an attribute name) to which they are prepended. C# supports the following special characters: +Special characters are predefined, contextual characters that modify the program element (a literal string, an identifier, or an attribute name) to which they're prepended. C# supports the following special characters: - [@](./verbatim.md), the verbatim identifier character. - [$](./interpolated.md), the interpolated string character. +- ["""](./raw-string.md), A sequence of three or more `"` characters provides the delimiters for a raw string literal. +- [_](./discard.md), a `_` character represents a *discard*, which is a placeholder for an unused variable. -This section only includes those tokens that are not operators. See the [operators](../operators/index.md) section for all operators. +This section only includes those tokens that aren't operators. See the [operators](../operators/index.md) section for all operators.