Skip to content

Commit c19c3ce

Browse files
authored
Update the tuples article(s) for C# 7.3 equality (#5390)
* final edit Add add the UDT equality example * respond to feedback * respond to feedback. Great comments all. * remove digression This suggests a tutorial that shows several idiomatic tuple scenarios.
1 parent fa7acdb commit c19c3ce

File tree

1 file changed

+73
-44
lines changed

1 file changed

+73
-44
lines changed

docs/csharp/tuples.md

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
---
2-
title: Tuples - C# Guide
2+
title: Tuple types - C# Guide
33
description: Learn about unnamed and named tuple types in C#
4-
ms.date: 11/23/2016
4+
ms.date: 05/15/2018
55
ms.assetid: ee8bf7c3-aa3e-4c9e-a5c6-e05cc6138baa
66
---
7-
# C# Tuple types #
7+
# C# tuple types #
88

9-
C# Tuples are types that you define using a lightweight syntax. The advantages
9+
C# tuples are types that you define using a lightweight syntax. The advantages
1010
include a simpler syntax, rules for conversions based on number (referred to as cardinality)
1111
and types of elements, and
12-
consistent rules for copies and assignments. As a tradeoff, Tuples do not
13-
support some of the object oriented idioms associated with inheritance. You
14-
can get an overview in the section on [Tuples in the What's new in C# 7.0](whats-new/csharp-7.md#tuples) topic.
12+
consistent rules for copies, equality tests, and assignments. As a tradeoff, tuples do not
13+
support some of the object-oriented idioms associated with inheritance. You
14+
can get an overview in the section on [tuples in the What's new in C# 7.0](whats-new/csharp-7.md#tuples) article.
1515

16-
In this topic, you'll learn the language rules governing Tuples in C# 7.0 and later,
17-
different ways to use them, and initial guidance on working with Tuples.
16+
In this article, you'll learn the language rules governing tuples in C# 7.0 and later versions,
17+
different ways to use them, and initial guidance on working with tuples.
1818

1919
> [!NOTE]
2020
> The new tuples features require the <xref:System.ValueTuple> types.
@@ -32,7 +32,7 @@ different ways to use them, and initial guidance on working with Tuples.
3232
> API and delivered as part of the framework, the NuGet package requirement will
3333
> be removed.
3434
35-
Let's start with the reasons for adding new Tuple support. Methods return
35+
Let's start with the reasons for adding new tuple support. Methods return
3636
a single object. Tuples enable you to package multiple values in that single
3737
object more easily.
3838

@@ -43,9 +43,9 @@ information. Using these `Tuple` types does not enable communicating the
4343
meaning of each of the properties. The new language features enable you to declare
4444
and use semantically meaningful names for the elements in a tuple.
4545

46-
Another concern is that the `Tuple` classes are
46+
The `Tuple` classes cause more performance concerns because they are
4747
reference types. Using one of the `Tuple` types means allocating objects. On hot
48-
paths, this can have a measurable impact on your application's performance. Therefore,
48+
paths, allocating many small objects can have a measurable impact on your application's performance. Therefore,
4949
the language support for tuples leverages the new `ValueTuple` structs.
5050

5151
To avoid those deficiencies, you could create a `class` or a `struct`
@@ -65,7 +65,7 @@ Tuples are both simpler and more flexible data containers than `class` and
6565

6666
## Named and unnamed tuples
6767

68-
The `ValueTuple` struct has fields named `Item1`, `Item2`, `Item3` and so on,
68+
The `ValueTuple` struct has fields named `Item1`, `Item2`, `Item3`, and so on,
6969
similar to the properties defined in the existing `Tuple` types.
7070
These names are the only names you can use for *unnamed tuples*. When you
7171
do not provide any alternative field names to a tuple, you've created an
@@ -74,7 +74,7 @@ unnamed tuple:
7474
[!code-csharp[UnnamedTuple](../../samples/snippets/csharp/tuples/tuples/program.cs#01_UnNamedTuple "Unnamed tuple")]
7575

7676
The tuple in the previous example was initialized using literal constants and
77-
won't have element names created using *Tuple field name projections* in C# 7.1.
77+
won't have element names created using *tuple field name projections* in C# 7.1.
7878

7979
However, when you initialize a tuple, you can use new language features
8080
that give better names to each field. Doing so creates a *named tuple*.
@@ -87,7 +87,7 @@ is to specify the names as part of the tuple initialization:
8787

8888
These synonyms are handled by the compiler and the language so that you
8989
can use named tuples effectively. IDEs and editors can read these semantic names
90-
using the Roslyn APIs. This enables you to reference the elements of a named
90+
using the Roslyn APIs. You can reference the elements of a named
9191
tuple by those semantic names anywhere in the same assembly. The compiler
9292
replaces the names you've defined with `Item*` equivalents when generating
9393
the compiled output. The compiled Microsoft Intermediate Language (MSIL)
@@ -103,7 +103,7 @@ The compiler must communicate those names you created for tuples that
103103
are returned from public methods or properties. In those cases, the compiler
104104
adds a <xref:System.Runtime.CompilerServices.TupleElementNamesAttribute> attribute on the method. This attribute contains
105105
a <xref:System.Runtime.CompilerServices.TupleElementNamesAttribute.TransformNames> list property that contains the names given to each of
106-
the elements in the Tuple.
106+
the elements in the tuple.
107107

108108
> [!NOTE]
109109
> Development Tools, such as Visual Studio, also read that metadata,
@@ -125,20 +125,20 @@ and `explicitFieldTwo`, not `localVariableOne` and `localVariableTwo`:
125125
[!code-csharp[ExplicitNamedTuple](../../samples/snippets/csharp/tuples/tuples/program.cs#ProjectionExample_Explicit "Explicitly named tuple")]
126126

127127
For any field where an explicit name is not provided, an applicable implicit
128-
name will be projected. Note that there is no requirement to provide semantic names,
129-
either explicitly or implicitly. The following initializer will have field
128+
name is projected. There is no requirement to provide semantic names,
129+
either explicitly or implicitly. The following initializer has field
130130
names `Item1`, whose value is `42` and `StringContent`, whose value is "The answer to everything":
131131

132132
[!code-csharp[MixedTuple](../../samples/snippets/csharp/tuples/tuples/program.cs#MixedTuple "mixed tuple")]
133133

134134
There are two conditions where candidate field names are not projected onto the tuple field:
135135

136-
1. When the candidate name is a reserved tuple name. Examples include `Item3`, `ToString` or `Rest`.
136+
1. When the candidate name is a reserved tuple name. Examples include `Item3`, `ToString`. or `Rest`.
137137
1. When the candidate name is a duplicate of another tuple field name, either explicit or implicit.
138138

139139
These conditions avoid ambiguity. These names would cause an ambiguity
140140
if they were used as the field names for a field in a tuple. Neither of these
141-
conditions cause compile time errors. Instead, the elements without projected names
141+
conditions cause compile-time errors. Instead, the elements without projected names
142142
do not have semantic names projected for them. The following examples
143143
demonstrate these conditions:
144144

@@ -147,11 +147,33 @@ demonstrate these conditions:
147147
These situations do not cause compiler errors because that would be a breaking change for
148148
code written with C# 7.0, when tuple field name projections were not available.
149149

150+
## Equality and tuples
151+
152+
Beginning with C# 7.3, tuple types support the `==` and `!=` operators. These operators work by comparing each member of the left argument to each member of the right argument in order. These comparisons short-circuit. The `==` operator stops evaluating members as soon as one pair is not equal. The `!=` operator stops evaluating members as soon as one pair is equal. The following code examples use `==`, but the comparison rules all apply to `!=`. The following code example shows an equality comparison for two pairs of integers:
153+
154+
[!code-csharp[TupleEquality](../../samples/snippets/csharp/tuples/tuples/program.cs#Equality "Testing tuples for equality")]
155+
156+
There are several rules that make tuple equality tests more convenient. Tuple equality performs [lifted conversions](/dotnet/csharp/language-reference/language-specification/conversions.md#lifted-conversion-operators) if one of the tuples is a nullable tuple, as shown in the following code:
157+
158+
[!code-csharp[NullableTupleEquality](../../samples/snippets/csharp/tuples/tuples/program.cs#NullableEquality "Comparing Tuples and nullable tuples")]
159+
160+
Tuple equality also performs implicit conversions on each member of both tuples. These include lifted conversions, widening conversions, or other implicit conversions. The following examples show that an integer 2-tuple can be compared to a long 2-tuple because of the implicit conversion from integer to long:
161+
162+
[!code-csharp[SnippetMemberConversions](../../samples/snippets/csharp/tuples/tuples/program.cs#SnippetMemberConversions "converting tuples for equality tests")]
163+
164+
The names of the tuple members do not participate in tests for equality. However, if one of the operands is a tuple literal with explicit names, the compiler generates warning CS8383 if those names do not match the names of the other operand.
165+
In the case where both operands are tuple literals, the warning is on the right operand as shown in the following example:
166+
167+
[!code-csharp[MemberNames](../../samples/snippets/csharp/tuples/tuples/program.cs#SnippetMemberNames "Tuple member names do not participate in equality tests")]
168+
169+
Finally, tuples may contain nested tuples. Tuple equality compares the "shape" of each operand through nested tuples as shown in the following example:
170+
171+
[!code-csharp[NestedTuples](../../samples/snippets/csharp/tuples/tuples/program.cs#SnippetNestedTuples "Tuples may contain nested tuples that participate in tuple equality.")]
172+
150173
## Assignment and tuples
151174

152175
The language supports assignment between tuple types that have
153-
the same number of elements and implicit conversions for the types for each of those
154-
elements. Other
176+
the same number of elements, where each right-hand side element can be implicitly converted to its corresponding left-hand side element. Other
155177
conversions are not considered for assignments. Let's look at the kinds
156178
of assignments that are allowed between tuple types.
157179

@@ -162,7 +184,7 @@ Consider these variables used in the following examples:
162184
The first two variables, `unnamed` and `anonymous` do not have semantic
163185
names provided for the elements. The field names are `Item1` and `Item2`.
164186
The last two variables, `named` and `differentName` have semantic names
165-
given for the elements. Note that these two tuples have different names
187+
given for the elements. These two tuples have different names
166188
for the elements.
167189

168190
All four of these tuples have the same number of elements (referred to as 'cardinality')
@@ -185,7 +207,7 @@ named = differentShape;
185207

186208
## Tuples as method return values
187209

188-
One of the most common uses for Tuples is as a method return
210+
One of the most common uses for tuples is as a method return
189211
value. Let's walk through one example. Consider this method
190212
that computes the standard deviation for a sequence of numbers:
191213

@@ -199,8 +221,8 @@ that computes the standard deviation for a sequence of numbers:
199221
> text for more details on the differences between these formulas
200222
> for standard deviation.
201223
202-
This follows the textbook formula for the standard deviation. It produces
203-
the correct answer, but it's a very inefficient implementation. This
224+
The preceding code follows the textbook formula for the standard deviation. It produces
225+
the correct answer, but it's an inefficient implementation. This
204226
method enumerates the sequence twice: Once to produce the average, and
205227
once to produce the average of the square of the difference of the average.
206228
(Remember that LINQ queries are evaluated lazily, so the computation of
@@ -214,14 +236,11 @@ and the sum of the each value squared:
214236

215237
[!code-csharp[SumOfSquaresFormula](../../samples/snippets/csharp/tuples/tuples/statistics.cs#06_SumOfSquaresFormula "Compute Standard Deviation using the sum of squares")]
216238

217-
This version enumerates the sequence exactly once. But, it's not very
218-
reusable code. As you keep working, you'll find that many different
239+
This version enumerates the sequence exactly once. But it's not reusable code. As you keep working, you'll find that many different
219240
statistical computations use the number of items in the sequence,
220-
the sum of the sequence, and the sum
241+
the sum of the sequence, and the sum
221242
of the squares of the sequence. Let's refactor this method and write
222-
a utility method that produces all three of those values.
223-
224-
This is where tuples come in very useful.
243+
a utility method that produces all three of those values. All three values can be returned as a tuple.
225244

226245
Let's update this method so the three values computed during the enumeration
227246
are stored in a tuple. That creates this version:
@@ -238,7 +257,7 @@ The language enables a couple more options that you can use, if you want
238257
to make a few quick edits by hand. First, you can use the `var`
239258
declaration to initialize the tuple result from the `ComputeSumAndSumOfSquares`
240259
method call. You can also create three discrete variables inside the
241-
`ComputeSumAndSumOfSquares` method. The final version is below:
260+
`ComputeSumAndSumOfSquares` method. The final version is shown in the following code:
242261

243262
[!code-csharp[CleanedTupleVersion](../../samples/snippets/csharp/tuples/tuples/statistics.cs#09_CleanedTupleVersion "After final cleanup")]
244263

@@ -269,12 +288,12 @@ private static (double, double, int) ComputeSumAndSumOfSquares(IEnumerable<doubl
269288
}
270289
```
271290

272-
You must address the fields of this tuple as `Item1`, `Item2`, and `Item3`.
291+
The fields of this tuple are named `Item1`, `Item2`, and `Item3`.
273292
It's recommended that you provide semantic names to the elements of tuples
274293
returned from methods.
275294

276-
Another idiom where tuples can be very useful is when you are authoring
277-
LINQ queries where the final result is a projection that contains some, but not
295+
Another idiom where tuples can be useful is when you are authoring
296+
LINQ queries. The final projected result often contains some, but not
278297
all, of the properties of the objects being selected.
279298

280299
You would traditionally project the results of the query into a sequence
@@ -293,7 +312,7 @@ class similar to the following to represent a single entry in the ToDo list:
293312
Your mobile applications may support a compact form of the current ToDo items
294313
that only displays the title. That LINQ query would make a projection that
295314
includes only the ID and the title. A method that returns a sequence of tuples
296-
expresses that design very well:
315+
expresses that design well:
297316

298317
[!code-csharp[QueryReturningTuple](../../samples/snippets/csharp/tuples/tuples/projectionsample.cs#15_QueryReturningTuple "Query returning a tuple")]
299318

@@ -328,7 +347,7 @@ declarations inside the parentheses.
328347
(double sum, var sumOfSquares, var count) = ComputeSumAndSumOfSquares(sequence);
329348
```
330349

331-
Note that you cannot use a specific
350+
You cannot use a specific
332351
type outside the parentheses, even if every field in the tuple has the
333352
same type.
334353

@@ -347,10 +366,10 @@ public class Point
347366
> [!WARNING]
348367
> You cannot mix existing declarations with declarations inside the parentheses. For instance, the following is not allowed: `(var x, y) = MyMethod();`. This produces error CS8184 because *x* is declared inside the parentheses and *y* is previously declared elsewhere.
349368
350-
### Deconstructing user defined types
369+
### Deconstructing user-defined types
351370

352371
Any tuple type can be deconstructed as shown above. It's also easy
353-
to enable deconstruction on any user defined type (classes, structs, or
372+
to enable deconstruction on any user-defined type (classes, structs, or
354373
even interfaces).
355374

356375
The type author can define one or more `Deconstruct` methods that
@@ -372,7 +391,7 @@ The `Deconstruct` method can be an extension method that unpackages
372391
the accessible data members of an object. The example below shows
373392
a `Student` type, derived from the `Person` type, and an extension
374393
method that deconstructs a `Student` into three variables, representing
375-
the `FirstName`, the `LastName` and the `GPA`:
394+
the `FirstName`, the `LastName`, and the `GPA`:
376395

377396
[!code-csharp[ExtensionDeconstructMethod](../../samples/snippets/csharp/tuples/tuples/person.cs#13_ExtensionDeconstructMethod "Type with a deconstruct extension method")]
378397

@@ -385,15 +404,25 @@ the last name are returned.
385404

386405
[!code-csharp[Deconstruct extension method](../../samples/snippets/csharp/tuples/tuples/program.cs#13A_DeconstructExtension "Deconstruct a class type using an extension method")]
387406

388-
You should be very careful defining multiple `Deconstruct` methods in a
407+
You should be careful defining multiple `Deconstruct` methods in a
389408
class or a class hierarchy. Multiple `Deconstruct` methods that have the
390409
same number of `out` parameters can quickly cause ambiguities. Callers may
391410
not be able to easily call the desired `Deconstruct` method.
392411

393-
In this example, there is minimal chance for an ambiguous call because the
412+
In this example, there is minimal chance for an ambiguous call because the
394413
`Deconstruct` method for `Person` has two output parameters, and the `Deconstruct`
395414
method for `Student` has three.
396415

416+
Deconstruction operators do not participate in testing equality. The following example generates compiler error CS0019:
417+
418+
```csharp
419+
Person p = new Person("Althea", "Goodwin");
420+
if (("Althea", "Goodwin") == p)
421+
Console.WriteLine(p);
422+
```
423+
424+
The `Deconstruct` method could convert the `Person` object `p` to a tuple containing two strings, but it is not applicable in the context of equality tests.
425+
397426
## Conclusion
398427

399428
The new language and library support for named tuples makes it much easier
@@ -402,6 +431,6 @@ but do not define behavior, as classes and structs do. It's
402431
easy and concise to use tuples for those types. You get all the benefits of
403432
static type checking, without needing to author types using the more
404433
verbose `class` or `struct` syntax. Even so, they are most useful for utility methods
405-
that are `private`, or `internal`. Create user defined types, either
434+
that are `private`, or `internal`. Create user-defined types, either
406435
`class` or `struct` types when your public methods return a value
407436
that has multiple elements.

0 commit comments

Comments
 (0)