diff --git a/standard/classes.md b/standard/classes.md index c5f5cf566..2c7e64f23 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -2955,6 +2955,18 @@ An extension method is a regular static method. In addition, where its enclosing > ``` > > *end example* + + + +> *Note*: Extension methods on a tuple type apply to tuples with different element names: +> +> ```csharp +> static void M(this (int x, int y) t) { ... } +> int a, int b) t = ...; +> t.M(); // OK +> ``` +> +> The extension method `M` is a candidate method, even though the tuple `t` has different element names (`a` and `b`) than the formal parameter of `M` (`x` and `y`). *end note*. ### 14.6.11 Method body diff --git a/standard/conversions.md b/standard/conversions.md index b701ad097..9a0dc7362 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -78,6 +78,32 @@ Because `object` and `dynamic` are considered equivalent there is an identity co In most cases, an identity conversion has no effect at runtime. However, since floating point operations may be performed at higher precision than prescribed by their type ([§8.3.7](types.md#837-floating-point-types)), assignment of their results may result in a loss of precision, and explicit casts are guaranteed to reduce precision to what is prescribed by the type ([§11.8.7](expressions.md#1187-cast-expressions)). +In tuple conversions, element names are immaterial. Tuples with the same arity are identity-convertible to each other or to and from corresponding underlying `ValueTuple` types, regardless of their element names. + +> *Example*: +> +> ```csharp +> var t = (sum: 0, count: 1); +> +> System.ValueTuple vt = t; // identity conversion +> (int moo, int boo) t2 = vt; // identity conversion +> +> t2.moo = 1; +> ``` +> +> *end example* + +In the case in which an element name at one position on one side of a conversion, and the same name at a different position on the other side, the compiler shall issue a warning. + +> *Example*: +> +> ```csharp +> (string first, string last) GetNames() { ... } +> (string last, string first) names = GetNames(); // Oops! +> ``` +> +> *end example* + ### 10.2.3 Implicit numeric conversions The implicit numeric conversions are: @@ -101,6 +127,20 @@ There are no predefined implicit conversions to the `char` type, so values of th An implicit enumeration conversion permits a *constant_expression* ([§11.20](expressions.md#1120-constant-expressions)) with any integer type and the value zero to be converted to any *enum_type* and to any *nullable_value_type* whose underlying type is an *enum_type*. In the latter case the conversion is evaluated by converting to the underlying *enum_type* and wrapping the result ([§8.3.11](types.md#8311-nullable-value-types)). +### §tuple-conversions-new-clause Tuple conversions + +Tuple types and expressions support a variety of conversions by "lifting" conversions of the elements into overall *tuple conversion*. For the classification purpose, all element conversions are considered recursively. For example, to have an implicit conversion, all element expressions/types shall have implicit conversions to the corresponding element types. + +Tuple conversions are *Standard Conversions*. + +An implicit tuple conversion is a standard conversion. It applies from one tuple type to another of equal arity when here is any implicit conversion from each element in the source tuple to the corresponding element in the destination tuple. + +An explicit tuple conversion is a standard conversion. It applies between two tuple types of equal arity when there is any explicit conversion between each corresponding pair of element types. + +A tuple conversion can be classified as a valid instance conversion or an extension method invocation as long as all element conversions are applicable as instance conversions. + +On top of the member-wise conversions implied by implicit typing, implicit conversions between tuple types themselves are allowed. + ### 10.2.5 Implicit interpolated string conversions An implicit interpolated string conversion permits an *interpolated_string_expression* ([§11.7.3](expressions.md#1173-interpolated-string-expressions)) to be converted to `System.IFormattable` or `System.FormattableString` (which implements `System.IFormattable`). @@ -152,6 +192,8 @@ A boxing conversion permits a *value_type* to be implicitly converted to a *refe - From any *nullable_value_type* to any *reference_type* where there is a boxing conversion from the underlying type of the *nullable_value_type* to the *reference_type.* - From a type parameter that is not known to be a reference type to any type such that the conversion is permitted by [§10.2.12](conversions.md#10212-implicit-conversions-involving-type-parameters). +Tuples have a boxing conversion. Importantly, the element names aren't part of the runtime representation of tuples, but are tracked only by the compiler. Thus, once element names have been "cast away," they cannot be recovered. In alignment with identity conversion, a boxed tuple unboxes to any tuple type that has the same arity. + Boxing a value of a *non-nullable-value-type* consists of allocating an object instance and copying the value into that instance. Boxing a value of a *nullable_value_type* produces a null reference if it is the null value (`HasValue` is false), or the result of unwrapping and boxing the underlying value otherwise. @@ -301,6 +343,45 @@ A user-defined implicit conversion consists of an optional standard implicit con Anonymous functions and method groups do not have types in and of themselves, but they may be implicitly converted to delegate types. Additionally, some lambda expressions may be implicitly converted to expression tree types. Anonymous function conversions are described in more detail in [§10.7](conversions.md#107-anonymous-function-conversions) and method group conversions in [§10.8](conversions.md#108-method-group-conversions). +### §tuple-literal-conversion-new-clause Tuple literal conversion + +A tuple literal is implicitly typed when used in a context specifying a tuple type. The tuple literal has a "conversion from expression" to any tuple type of the same arity, as long as the element expressions of the tuple literal have an implicit conversion to the corresponding element types of the tuple type. + +> *Example*: +> +> ```csharp +> (string name, byte age) t = (null, 5); // OK: the expressions null and 5 convert to string and byte +> ``` +> +> *end example* + +A successful conversion from tuple expression to tuple type is classified as an *ImplicitTuple* conversion, unless the tuple's natural type (§tuple-types-general-new-clause) matches the target type exactly, in such case it is an *Identity* conversion. + +> *Example*: +> +> ```csharp +> void M1((int x, int y) arg){...}; +> void M1((object x, object y) arg){...}; +> +> M1((1, 2)); // first overload is used. Identity conversion is better than implicit conversion. +> M1(("hi", "hello")); // second overload is used. Implicit tuple conversion is better than no conversion. +> ``` +> +> *end example* + +A successful conversion from tuple expression to a nullable tuple type is classified as *ImplicitNullable* conversion. + +> *Example*: +> +> ```csharp +> ((int x, int y, int z)?, int t)? SpaceTime() +> { +> return ((1,2,3), 7); // valid, implicit nullable conversion +> } +> ``` +> +> *end example* + ## 10.3 Explicit conversions ### 10.3.1 General diff --git a/standard/expressions.md b/standard/expressions.md index e0823b247..7a5d926eb 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1064,6 +1064,10 @@ Given two types `T₁` and `T₂`, `T₁` is a ***better conversion target*** th > > *end example* +#### §overload-resolution-and-tuples-new-clause Overload resolution and tuples with no natural types + +The exact-match rule for tuple expressions is based on the natural types (§tuple-types-general-new-clause) of the constituent tuple elements. The rule is mutually recursive with respect to other containing or contained expressions not in a possession of a natural type. + ### 11.6.5 Compile-time checking of dynamic member invocation Even though overload resolution of a dynamically bound operation takes place at run-time, it is sometimes possible at compile-time to know the list of function members from which an overload will be chosen: @@ -1150,6 +1154,7 @@ primary_expression primary_no_array_creation_expression : literal | interpolated_string_expression + | tuple_literal | simple_name | parenthesized_expression | member_access @@ -1402,6 +1407,58 @@ Then: *end example* +### §tuple-literal-expressions-new-clause Tuple literal expressions + +A tuple literal consists of two or more tuple literal elements, each of which is optionally named. + +```ANTLR +tuple_literal + : '(' ( tuple_literal_element ',' )+ tuple_literal_element ')' + ; +tuple_literal_element + : ( identifier ':' )? expression + ; +``` + +A tuple literal is implicitly typed; that is, its type is determined by the context in which it is used, referred to as the ***target***. Each element *expression* in a tuple literal shall have a value that can be converted implicitly to its corresponding target element type. + +> *Example*: +> +> ```csharp +> var t1 = (0, 2); // infer tuple type (int, int) from values +> var t2 = (sum: 0, count: 1); // infer tuple type (int sum, int count) from names and values +> (int, double) t3 = (0, 2); // infer tuple type (int, double) from values; can implicitly convert int to double +> (int, double) t4 = (0.0, 2); // Error: can't implicitly convert double to int +> ``` +> +> *end example* + +A tuple literal has a "conversion from expression" to any tuple type of the same arity, as long as each of the element expressions of the tuple literal has an implicit conversion to the type of the corresponding element of the tuple type. + +> *Example*: +> +> ```csharp +> (string name, byte age) t = (null, 5); // OK: null and 5 convert to string and byte, respectively +> ``` +> +> *end example* + +In cases where a tuple literal is not part of a conversion, the literal's type is its natural type (§tuple-types-general-new-clause), if one exists. + +> *Example*: +> +> ```csharp +> var t = ("John", 5); // OK: the natural type is (string, int) +> var t = (null, 5); // Error: null doesn't have a type +> var t = (name: "John", age: 5); // OK: The natural type is (string name, int age) +> ``` +> +> *end example* + +A tuple literal is *not* a [constant expression](expressions.md#1120-constant-expressions). + +For a discussion of tuple literals as tuple initializers, see §tuple-types-new-clause. + ### 11.7.4 Simple names A *simple_name* consists of an identifier, optionally followed by a type argument list: @@ -3787,6 +3844,65 @@ The result of evaluating `x «op» y`, where x and y are expressions of an enu Lifted ([§11.4.8](expressions.md#1148-lifted-operators)) forms of the unlifted predefined enumeration comparison operators defined above are also predefined. +### §tuple-comparison-operators-new Tuple comparison operators + +The predefined tuple equality operators are: + +```csharp +bool operator ==(Tup1 t1, Tup2 t2); +bool operator !=(Tup1 t1, Tup2 t2); +``` + +For any tuple or nullable tuple types `Tup1` and `Tup2`, `t1` and `t2` shall have the same number of elements, and operators `==` and `!=` shall be defined for the types of each corresponding element pair. Consider the following: + +```csharp +(0, "abc") == (1, "xy") // OK; same number of elements with == defined for int and string +(0, "abc") == (1.0, "xy") // OK; same number of elements with == defined for int/double +(0, "abc") != (0L, "xy") // OK; same number of elements with == defined for int/long +(0, "abc") != ("xy", 2) // Error; == not defined for int/string or string/int +(0, "abc") == (1, "xy", 10) // Error; different number of elements +``` + +The result of `==` is `true` if the values in each corresponding element pair compare equal using the operator `==` for their types. Otherwise, the result is `false`. + +The result of `!=` is `true` if any comparison of the values in each corresponding element pair compare unequal using the operator `!=` for their types. Otherwise, the result is `false`. + +Both operands are evaluated in order from left-to-right. Each pair of elements is then used as operands to bind the operator `==` (or `!=`), recursively. Any elements with compile-time type `dynamic` cause an error. + +Element names are ignored during tuple comparison. + +When a tuple literal ((§tuple-literal-expressions-new-clause) is used as an operand, it takes on a converted tuple type formed by the element-wise conversions that are introduced when binding the operator `==` (or `!=`) element-wise. For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for both tuple literals is `(long, long, string)` and the second literal has no natural type (§tuple-types-general-new-clause). + +In the nullable tuple case, additional checks for `t1.HasValue` and/or `t2.HasValue` shall be performed. + +When an element-wise comparison returns a non-`bool` result, if that comparison is dynamic in a tuple equality, a dynamic invocation of the operator `false` shall be used with the result being negated to get a `bool`. + +If an element-wise comparison returns some other non-`bool` type in a tuple equality, there are two cases: + +- if the non-bool type converts to `bool`, that conversion is applied, +- if there is no such conversion, but the type has an operator `false`, that is used the result is negated. + +In a tuple inequality, the same rules apply except that the operator `true` is used without negation. + +When binding the `==` (or `!=`) operator, the usual rules are: (1) dynamic case, (2) overload resolution, and (3) fail. However, in the case of tuple comparison, a new rule is inserted between (1) and (2): if both operands of a comparison operator are tuples, the comparison is performed elementwise. This tuple equality is also lifted onto nullable tuples. + +> *Note*: If prior to the addition of tuple comparison to C#, a program defined `ValueTuple` types with `==` or `!=` operators, those operators would have been chosen by overload resolution. However, with the addition of comparison support and the new rule above, the comparison is handled by tuple comparison instead of the user-defined comparison. *end note* + +Regarding the order of evaluation of `t1` and `t2`, `t1` is evaluated first followed by `t2`, then the element-wise comparisons going from left-to-right. Consider the following: if there is a conversion from type `A` to type `B` and a method `(A, A) GetTuple()`, the comparison + +```csharp +(new A(1), (new B(2), new B(3))) == (new B(4), GetTuple()) +``` + +is evaluated thus: + +- `new A(1)` +- `new B(2)` +- `new B(3)` +- `new B(4)` +- `GetTuple()` +- then the element-wise conversions and comparisons and conditional logic is evaluated (convert `new A(1)` to type `B`, then compare it with `new B(4)`, and so on). + ### 11.11.7 Reference type equality operators Every class type `C` implicitly provides the following predefined reference type equality operators: @@ -3950,6 +4066,9 @@ x == null null == x x != null null != x where `x` is an expression of a nullable value type, if operator overload resolution ([§11.4.5](expressions.md#1145-binary-operator-overload-resolution)) fails to find an applicable operator, the result is instead computed from the `HasValue` property of `x`. Specifically, the first two forms are translated into `!x.HasValue`, and the last two forms are translated into `x.HasValue`. +In tuple equality, expressions such as `(0, null) == (0, null)` and `(0, null) != (0, null)` are allowed with neither `null` nor the tuple literals having a type. +Consider the case of a type `struct S` without `operator==`. The comparison `(S?)x == null` is allowed, and it is interpreted as `((S?).x).HasValue`. In tuple equality, the same rule is applied, so `(0, (S?)x) == (0, null)` is also allowed. + ### 11.11.11 The is operator The `is` operator is used to check if the run-time type of an object is compatible with a given type. The check is performed at runtime. The result of the operation `E is T`, where `E` is an expression and `T` is a type other than `dynamic`, is a Boolean value indicating whether `E` is non-null and can successfully be converted to type `T` by a reference conversion, a boxing conversion, an unboxing conversion, a wrapping conversion, or an unwrapping conversion. @@ -5862,6 +5981,117 @@ If the left operand of `a += or -=` operator is classified as an event access, An event assignment expression does not yield a value. Thus, an event assignment expression is valid only in the context of a *statement_expression* ([§12.7](statements.md#127-expression-statements)). +### §deconstruction-expressions-new-clause Deconstruction expressions + +A tuple-deconstruction expression copies from a source tuple zero or more of its element values to corresponding destinations. + +```ANTLR +tuple_deconstruction_expression + : '(' destination_list ')' + ; + +destination_list + : destination ',' destination + | destination_list ',' destination + ; + +destination + : local_variable_type? identifier + ; +``` + +Element values are copied from the source tuple to the destination(s). Each element's position is inferred from the destination position within *destination_list*. If no variable called “_” is in scope, a *destination* with *identifier* `_` indicates a discard (§discards-new-clause), and the corresponding element is discarded rather than being copied. (Discards are discussed further below.) The destination list shall account for every element in the tuple. + +> *Example*: +> +> ```csharp +> int code; +> string message; +> (code, message) = (10, "hello"); // copy both element values to existing variables +> (code, _) = (11, "Go!"); // copy element 1 to code and discard element 2 +> (_, _) = (12, "Stop!"); // discard both element values +> (int code2, string message2) = (20, "left"); // copy both element values to newly created variables +> (code, string message3) = (21, "right"); // Error: can't mix existing and new variables +> (code, _) = (30, 2.5, (10, 20)); // Error: can't deconstruct tuple of 3 elements into 2 values +> (code, _, _) = (30, 2.5, (10, 20)); // OK: deconstructing 3 elements into 3 values +> ``` +> +> *end example* + +Any object may be deconstructed by providing an accessible `Deconstruct` method, either as an instance member or as an extension method. A `Deconstruct` method converts an object to a set of discrete values. The Deconstruct method "returns" the component values by use of individual `out` parameters. `Deconstruct` is overloadable. Consider the following: + +```csharp +class Name +{ + public void Deconstruct(out string first, out string last) { + first = First; last = Last; + } + ... +} +static class Extensions +{ + public static void Deconstruct(this Name name, out string first, out string last) { + first = name.First; last = name.Last; + } +} +``` + +Overload resolution for `Deconstruct` methods considers only the arity of the `Deconstruct` method. If multiple `Deconstruct` methods of the same arity are accessible, the expression is ambiguous and a binding-time error shall occur. + +If necessary, to satisfy implicit conversions of the tuple member types, the compiler passes temporary variables to the `Deconstruct` method, instead of the ones declared in the deconstruction. For example, if object `p` has the following method: + +```csharp +void Deconstruct(out byte x, out byte y) ...; +``` + +the compiler translates + +```csharp +(int x, int y) = p; +``` + +to: + +```csharp +p.Deconstruct(out byte __x, out byte __y); +(int x, int y) = (__x, __y); +``` + +The evaluation order of deconstruction assignment expressions is "breadth first;" that is, left-to-right. Each element is then copied as defined by [§11.18.2](expressions.md#11182-simple-assignment). + +> *Example*: +> +> ```csharp +> string x; +> byte y; +> +> (x, y) = (y, x); // swap! +> ``` +> +> *end example* + +A deconstructing assignment is a *statement-expression* whose type could be `void`. + +Consider the following, in which a variable called `_` is defined: + +```csharp +string s; +var d = 1.0; +int i; +char _; +(s, d, i, _) = ("abc", 20.5, 10, 'X'); +``` + +In this case, the target of the fourth assignment is that variable. + +Contrast this with the following, in which no variable called `_` is defined: + +```csharp +(_, var _, int _, char c) = ("abc", 20.5, 10, 'X'); +``` + +The three uses of `_` are discards. The first has no type, and none is needed. The second has type `var`, which is compatible with `double`, the type of its corresponding source. The third has type `int`, which is compatible with `int`, the type of its corresponding source. + ## 11.19 Expression An *expression* is either a *non_assignment_expression* or an *assignment*. diff --git a/standard/standard-library.md b/standard/standard-library.md index 8e94f52fa..cbcd4b7ed 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -446,6 +446,181 @@ namespace System.Threading.Tasks public new System.Runtime.CompilerServices.TaskAwaiter GetAwaiter(); } } + +namespace System +{ + public struct ValueTuple<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2> : IStructuralComparable, + IStructuralEquatable, IComparable, IComparable<(T1, T2)>, IEquatable<(T1, T2)>, ITuple + { + [NullableAttribute(1)] + public T1 Item1; + [NullableAttribute(1)] + public T2 Item2; + [NullableContextAttribute(1)] + public ValueTuple(T1 item1, T2 item2); + public int CompareTo([NullableAttribute(new[] { 0, 1, 1 })] (T1, T2) other); + [NullableContextAttribute(2)] + public override bool Equals(object? obj); + public bool Equals([NullableAttribute(new[] { 0, 1, 1 })] (T1, T2) other); + public override int GetHashCode(); + [NullableContextAttribute(1)] + public override string ToString(); + } +} + +namespace System +{ + public struct ValueTuple<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3> + : IStructuralComparable, IStructuralEquatable, IComparable, IComparable<(T1, T2, T3)>, + IEquatable<(T1, T2, T3)>, ITuple + { + [NullableAttribute(1)] + public T1 Item1; + [NullableAttribute(1)] + public T2 Item2; + [NullableAttribute(1)] + public T3 Item3; + [NullableContextAttribute(1)] + public ValueTuple(T1 item1, T2 item2, T3 item3); + public int CompareTo([NullableAttribute(new[] { 0, 1, 1, 1 })] (T1, T2, T3) other); + [NullableContextAttribute(2)] + public override bool Equals(object? obj); + public bool Equals([NullableAttribute(new[] { 0, 1, 1, 1 })] (T1, T2, T3) other); + public override int GetHashCode(); + [NullableContextAttribute(1)] + public override string ToString(); + } +} + +namespace System +{ + [NullableAttribute(0)] + [NullableContextAttribute(1)] + public struct ValueTuple<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, + [NullableAttribute(2)] T4> : IStructuralComparable, IStructuralEquatable, IComparable, + IComparable<(T1, T2, T3, T4)>, IEquatable<(T1, T2, T3, T4)>, ITuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4); + public int CompareTo([NullableAttribute(new[] { 0, 1, 1, 1, 1 })] (T1, T2, T3, T4) other); + [NullableContextAttribute(2)] + public override bool Equals(object? obj); + public bool Equals([NullableAttribute(new[] { 0, 1, 1, 1, 1 })] (T1, T2, T3, T4) other); + public override int GetHashCode(); + public override string ToString(); + } +} + +namespace System +{ + [NullableAttribute(0)] + [NullableContextAttribute(1)] + public struct ValueTuple<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, + [NullableAttribute(2)] T4, [NullableAttribute(2)] T5> : IStructuralComparable, IStructuralEquatable, + IComparable, IComparable<(T1, T2, T3, T4, T5)>, IEquatable<(T1, T2, T3, T4, T5)>, ITuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public T5 Item5; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5); + public int CompareTo([NullableAttribute(new[] { 0, 1, 1, 1, 1, 1 })] (T1, T2, T3, T4, T5) other); + [NullableContextAttribute(2)] + public override bool Equals(object? obj); + public bool Equals([NullableAttribute(new[] { 0, 1, 1, 1, 1, 1 })] (T1, T2, T3, T4, T5) other); + public override int GetHashCode(); + public override string ToString(); + } +} + +namespace System +{ + [NullableAttribute(0)] + [NullableContextAttribute(1)] + public struct ValueTuple<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, + [NullableAttribute(2)] T4, [NullableAttribute(2)] T5, [NullableAttribute(2)] T6> : IStructuralComparable, + IStructuralEquatable, IComparable, IComparable<(T1, T2, T3, T4, T5, T6)>, + IEquatable<(T1, T2, T3, T4, T5, T6)>, ITuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public T5 Item5; + public T6 Item6; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6); + public int CompareTo([NullableAttribute(new[] { 0, 1, 1, 1, 1, 1, 1 })] (T1, T2, T3, T4, T5, T6) other); + [NullableContextAttribute(2)] + public override bool Equals(object? obj); + public bool Equals([NullableAttribute(new[] { 0, 1, 1, 1, 1, 1, 1 })] (T1, T2, T3, T4, T5, T6) other); + public override int GetHashCode(); + public override string ToString(); + } +} + +namespace System +{ + [NullableAttribute(0)] + [NullableContextAttribute(1)] + public struct ValueTuple<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, + [NullableAttribute(2)] T4, [NullableAttribute(2)] T5, [NullableAttribute(2)] T6, [NullableAttribute(2)] T7> + : IStructuralComparable, IStructuralEquatable, IComparable, IComparable<(T1, T2, T3, T4, T5, T6, T7)>, + IEquatable<(T1, T2, T3, T4, T5, T6, T7)>, ITuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public T5 Item5; + public T6 Item6; + public T7 Item7; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7); + public int CompareTo([NullableAttribute(new[] { 0, 1, 1, 1, 1, 1, 1, 1 })] + (T1, T2, T3, T4, T5, T6, T7) other); + [NullableContextAttribute(2)] + public override bool Equals(object? obj); + public bool Equals([NullableAttribute(new[] { 0, 1, 1, 1, 1, 1, 1, 1 })] + (T1, T2, T3, T4, T5, T6, T7) other); + public override int GetHashCode(); + public override string ToString(); + } +} + +namespace System +{ + [NullableAttribute(0)] + [NullableContextAttribute(1)] + public struct ValueTuple<[NullableAttribute(2)] T1, [NullableAttribute(2)] T2, [NullableAttribute(2)] T3, + [NullableAttribute(2)] T4, [NullableAttribute(2)] T5, [NullableAttribute(2)] T6, [NullableAttribute(2)] T7, + [NullableAttribute(0)] TRest> : IStructuralComparable, IStructuralEquatable, IComparable, + IComparable>, + IEquatable>, ITuple where TRest : struct + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public T5 Item5; + public T6 Item6; + public T7 Item7; + [NullableAttribute(0)] + public TRest Rest; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, + [NullableAttribute(0)] TRest rest); + public int CompareTo([NullableAttribute(new[] { 0, 1, 1, 1, 1, 1, 1, 1, 0 })] + ValueTuple other); + [NullableContextAttribute(2)] + public override bool Equals(object? obj); + public bool Equals([NullableAttribute(new[] { 0, 1, 1, 1, 1, 1, 1, 1, 0 })] + ValueTuple other); + public override int GetHashCode(); + public override string ToString(); + } +} ``` ## C.4 Format Specifications diff --git a/standard/types.md b/standard/types.md index c751a6ce9..7dabec3f8 100644 --- a/standard/types.md +++ b/standard/types.md @@ -403,6 +403,187 @@ Implicit conversions are available from the `null` literal to `T?` ([§10.2.7](c The nullable type `T?` implements no interfaces ([§17](interfaces.md#17-interfaces)). In particular, this means it does not implement any interface that the underlying type `T` does. +### §tuple-types-new-clause Tuple types + +#### §tuple-types-general-new-clause General + +A tuple is declared using the following syntax: + +```ANTLR +tuple_type + : '(' tuple_type_element_list ')' + ; + +tuple_type_element_list + : tuple_type_element ',' tuple_type_element + | tuple_type_element_list ',' tuple_type_element + ; + +tuple_type_element + : type identifier? + ; +``` + +A ***tuple*** is an anonymous data structure type that contains an ordered sequence of two or more ***elements***, which are optionally named. Each element is public. If a tuple is mutable, its element values are also mutable. + +A tuple's ***natural type*** is the combination of its element types, in lexical order, and element names, if they exist. + +A tuple's ***arity*** is the combination of its element types, in lexical order; element names are *not* included. Each unique tuple arity designates a distinct tuple type. + +Two tuple values are equal if they have the same arity, and the values of the elements in each corresponding element pair are equal. + +An element in a tuple is accessed using the member-access operator `.` ([§11.7.6](expressions.md#1176-member-access). + +Given the following, + +```csharp +(int code, string message) pair1 = (3, "hello"); +System.Console.WriteLine("first = {0}, second = {1}", pair1.code, pair1.message); +``` + +the syntax `(int code, string message)` declares a tuple type having two elements, each with the given type and name. + +As shown, a tuple can be initialized using a tuple literal (§tuple-literal-expressions-new-clause). + +An element need not have a name. An element without a name is unnamed. + +If a tuple declarator contains the type of all the tuple's elements, that set of types cannot be changed or augmented based on the context in which it is used; otherwise, element type information shall be inferred from the usage context. Likewise, for element names. + +A tuple's type can be declared explicitly. Consider the following declarations: + +```csharp +(int, string) pair2 = (2, "Goodbye"); +(int code, string message) pair3 = (2, "Goodbye"); +(int code, string) pair4 = (2, "Goodbye"); +(int, string message) pair5 = (2, "Goodbye"); +(int code, string) pair6 = (2, message: "Goodbye"); // Warning: can't give a name to the second element +(int code, string) pair7 = (newName: 2, "Goodbye"); // Warning: can't change the name of element code +``` + +The type of `pair2` is `(int, string)` with unknown element names. Similarly, the type of `pair3` is `(int, string)` but this time with the element names `code` and `message`, respectively. For `pair4` and `pair5`, one element is named, the other not. + +In the case of `pair6`, the second element is declared as being unnamed, and any attempt to provide a name in an initializing context shall be ignored. Likewise, for any attempt to change an element's name, as in the case of `pair7`. + +A tuple's type can be wholly inferred from the context in which it is used. Consider the following declarations: + +```csharp +var pair10 = (1, "Hello"); +var pair11 = (code: 1, message: "Hello"); +var pair12 = (code: 1, "Hello"); +var pair13 = (1, message: "Hello"); +``` + +The type of `pair10` is inferred from the initializer's tuple literal, as `(int, string)` with unknown element names. Similarly, the type of `pair11` is inferred from the initializer’s tuple literal, as `(int, string)` but this time with the element names `code` and `message`, respectively. For `pair12` and `pair13`, the element types are inferred, and one element is named, the other not. + +Element names within a tuple type shall be distinct. + +> *Example*: +> +> ```csharp +> (int e1, float e1) t = (10, 1.2); // Error: both elements have the same name +> (int e1, (int e1, int e2) e2) t = (10, (20, 30)); // OK: element names in each tuple type are distinct +> ``` +> +> *end example* + +The name of any element in a partial type declaration shall be the same for an element in the same position in any other partial declaration for that type. + +> *Example*: +> +> ```csharp +> partial class C : IEnumerable<(string name, int age)> { ... } +> partial class C : IEnumerable<(string fullname, int)> { ... } // Error: names must be specified and the same +> ``` +> +> *end example* + +A tuple cannot be created with the `new` operator. However, the `new` operator can be used to create and initialize an array of tuple or a nullable tuple. + +#### §tuple-underlying-type-new-clause A tuple's underlying type + +Each tuple type maps to an underlying type. Specifically, a tuple having two elements maps to `System.ValueTuple`, one with three elements maps to `System.ValueTuple`, and so on, up to seven elements. Tuple types having eight or more elements map to `System.ValueTuple`. The first element in an underlying type has the public name `Item1`, the second `Item2`, and so on through `Item7`. Any elements beyond seven can be accessed as a group by the public name `Rest`, whose type is "tuple of the remaining elements". Alternatively, those elements can be accessed individually using the names `Item8` through `Item`*N*, where *N* is the total number of elements, even though the underlying type has no such names defined. +A tuple type shall behave exactly like its underlying type. The only additional enhancement in the tuple type case is the ability to provide a more expressive name for each element. + +> *Example*: +> +> ```csharp +> var t1 = (sum: 0, 1); +> t1.sum = 1; // access the first element by its declared name +> t1.Item1 = 1; // access the first element by its underlying name +> t1.Item2 = 3; // access the second element by its underlying name +> +> System.ValueTuple vt = t1; // identity conversion +> +> var t2 = (1, 2, 3, 4, 5, 6, 7, 8, 9); // t2 is a System.ValueTuple +> var t3 = t4.Rest; // t3 is a (int, int); that is, a System.ValueTuple +> System.Console.WriteLine("Item9 = {0}", t1.Item9); // outputs 9 even though no such name Item9 exists! +> ``` +> +> *end example* + + + +> *Example*: +> +> ```csharp +> var t = (ToString: 0, GetHashCode: 1); // Error: names match underlying member names +> var t1 = (Item1: 0, Item2: 1); // OK +> var t2 = (misc: 0, Item1: 1); // Error: "Item1" used in a wrong position +> ``` +> +> *end example* + +#### §element-names-and-overloading-new-clause Element names and overloading, overriding, and hiding + +When tuple element names are used in overridden signatures or implementations of interface methods, tuple element names in parameter and return types shall be preserved. It is an error for the same generic interface to be inherited or implemented twice with identity-convertible type arguments that have conflicting tuple element names. + +For the purpose of overloading, overriding, and hiding, tuples of the same arity, as well as their underlying `ValueTuple` types, shall be considered equivalent. All other differences are immaterial. When overriding a member, it shall be permitted to use tuple types with the same element names or element names different than in the base member. + +If the same element name is used for non-matching elements in base and derived member signatures, the implementation shall issue a warning. + +> *Example*: +> +> ```csharp +> public class Base +> { +> public virtual void M1(ValueTuple arg){...} +> } +> public class Derived : Base +> { +> public override void M1((int c, int d) arg){...} // valid override, signatures are equivalent +> } +> public class Derived2 : Derived +> { +> public override void M1((int c1, int c) arg){...} // also valid, warning on possible misuse of name 'c' +> } +> +> public class InvalidOverloading +> { +> public virtual void M1((int c, int d) arg){...} +> public virtual void M1((int x, int y) arg){...} // invalid overload, signatures are eqivalent +> public virtual void M1(ValueTuple arg){...} // also invalid +> } +> ``` +> +> *end example* + +#### §tuple-element-name-erasure-new-clause Tuple element name erasure at runtime + +A tuple element name is not part of the runtime representation of a tuple of that type; an element's name is tracked only by the compiler. + +> *Note*: As a result, element names are not available to a third-party observer of a tuple instance (such as with reflection or dynamic code). *end note* + +In alignment with the identity conversions, a boxed tuple shall not retain the names of the elements, and shall unbox to any tuple type that has the same element types in the same order. + +> *Example*: +> +> ```csharp +> object o = (a: 1, b: 2); // boxing conversion +> var t = ((int moo, int boo))o; // unboxing conversion +> ``` +> +> *end example* + ### 8.3.12 Boxing and unboxing The concept of boxing and unboxing provide a bridge between *value_type*s and *reference_type*s by permitting any value of a *value_type* to be converted to and from type `object`. Boxing and unboxing enables a unified view of the type system wherein a value of any type can ultimately be treated as an `object`.