diff --git a/standard/attributes.md b/standard/attributes.md index b9353466f..8b180a54c 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -506,6 +506,7 @@ A number of attributes affect the language in some way. These attributes include - `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute` and `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`, which are used to declare a custom interpolated string expression handler ([§23.5.10.1](attributes.md#235101-custom-interpolated-string-expression-handlers)) and to call one of its constructors, respectively. - System.Diagnostics.CodeAnalysis.UnscopedRefAttribute ([§23.5.8](attributes.md#2358-the-unscopedref-attribute)), which allows an otherwise implicitly scoped ref to be treated as not being scoped. - `System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute` ([§23.5.11.1](attributes.md#235111-the-setsrequiredmembers-attribute)) and `System.Runtime.CompilerServices.RequiredMemberAttribute` ([§23.5.11.2](attributes.md#235112-the-requiredmember-attribute)), which are used in required-member contexts ([§15.7.1](classes.md#1571-general)). +- `System.Runtime.CompilerServices.CollectionBuilderAttribute` (§collection-builder-attr), which designates a collection type as having a collection-creation method. The Nullable static analysis attributes ([§23.5.7](attributes.md#2357-code-analysis-attributes)) can improve the correctness of warnings generated for nullabilities and null states ([§8.9.5](types.md#895-nullabilities-and-null-states)). @@ -1562,6 +1563,16 @@ This attribute indicates that the constructor it decorates sets all required mem This attribute indicates that the current type has one or more required members ([§15.7.1](classes.md#1571-general)), or that a specific member of that type is required. However, it is an error for this attribute to be used explicitly. Instead, the presence of the modifier `required` results in the type or member being treated as if it were decorated with this attribute. +### §collection-builder-attr The CollectionBuilder attribute + +This attribute designates a collection type as having a collection-creation method (§declaring-a-collection-type-general). + +The constructor takes a builder type and the name of the method to be invoked to construct an instance of the collection type. + +The attribute can be applied to a class, struct, ref struct, or interface. The attribute is not inherited although it can be applied to a base class or an abstract class. + +The builder type shall be a non-generic class or struct. + ## 23.6 Attributes for interoperation For interoperation with other languages, an indexer may be implemented using indexed properties. If no `IndexerName` attribute is present for an indexer, then the name `Item` is used by default. The `IndexerName` attribute enables a developer to override this default and specify a different name. diff --git a/standard/classes.md b/standard/classes.md index 88591cfb1..b36640954 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -2551,6 +2551,8 @@ Except for allowing a variable number of arguments in an invocation, a parameter > F(arr); > F(10, 20, 30, 40); > F(); +> F([-5, 3, 9]); +> F([]); > } > } > ``` @@ -2561,6 +2563,8 @@ Except for allowing a variable number of arguments in an invocation, a parameter > Array contains 3 elements: 1 2 3 > Array contains 4 elements: 10 20 30 40 > Array contains 0 elements: +> Array contains 3 elements: -5 3 9 +> Array contains 0 elements: > ``` > > The first invocation of `F` simply passes the array `arr` as a value parameter. The second invocation of F automatically creates a four-element `int[]` with the given element values and passes that array instance as a value parameter. Likewise, the third invocation of `F` creates a zero-element `int[]` and passes that instance as a value parameter. The second and third invocations are precisely equivalent to writing: @@ -2570,7 +2574,7 @@ Except for allowing a variable number of arguments in an invocation, a parameter > F(new int[] {}); > ``` > -> *end example* +> The fourth and fifth invocations pass a three-element and an empty collection expression, respectively. *end example* When performing overload resolution, a method with a parameter array might be applicable, either in its normal form or in its expanded form ([§12.6.4.2](expressions.md#12642-applicable-function-member)). The expanded form of a method is available only if the normal form of the method is not applicable and only if an applicable method with the same signature as the expanded form is not already declared in the same type. @@ -6751,3 +6755,154 @@ A positional record class ([§15.2.1](classes.md#1521-general)) with at least on > ``` > > *end example* + +## §declaring-a-collection-type Declaring a collection type + +### §declaring-a-collection-type-general General + +There are a number of contexts in which a collection expression (§collection-expressions) may be converted to a collection type (§imp-collection-expression-conv). One of them is for a target class, struct, or interface type to be made a collection type by annotating it with an attribute, as shown below. + +Here is a simple user-defined collection type and its associated builder type: + + +```csharp +[CollectionBuilder(typeof(MyCollectionBuilder), "Create")] +public class MyCollection : IEnumerable +{ + private readonly T[] _storage; + public int Count { get; } + public T this[int index] + { + get + { + return _storage[index]; + } + + set + { + _storage[index] = value; + } + } + public MyCollection(ReadOnlySpan elements) + { + Count = elements.Length; + _storage = new T[Count]; + for (int i = 0; i < Count; i++) + { + _storage[i] = elements[i]; + } + } + public IEnumerator GetEnumerator() => + _storage.AsEnumerable().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + _storage.GetEnumerator(); +} +internal static class MyCollectionBuilder +{ + internal static MyCollection + Create(ReadOnlySpan values) => + new MyCollection(values); +} +``` + +The collection type shall be annotated with a `CollectionBuilder` attribute (§collection-builder-attr) that designates an associated, non-generic builder class or struct type having a collection-creation method (whose name is user-defined; in this case, it is `Create`). + +The job of a ***collection-creation method*** is to create and initialize an instance of its associated collection type. + +First, the set of applicable collection-creation methods `CM` is determined. It consists of methods that meet the following requirements: + +- Be directly declared in the builder type +- Be static +- Be accessible at its point of use +- Its arity shall match that of the collection type +- It shall have a single parameter of type `System.ReadOnlySpan`, passed by value. +- There shall be an implicit identity conversion ([§10.2.2](conversions.md#1022-identity-conversion)), an implicit reference conversion ([§10.2.8](conversions.md#1028-implicit-reference-conversions)), or a boxing conversion ([§10.2.9](conversions.md#1029-boxing-conversions)) from the method return type to the collection type. +- It shall return an instance of the collection being built, which contains a copy of the data from the span parameter. + +Methods declared on base types or interfaces are ignored and not part of the `CM` set. + +If the `CM` set is empty, then the collection type doesn't have an element type, doesn't have a collection-creation method, and none of the following steps apply. + +If only one method among those in the `CM` set has an identity conversion from `E` to the element type of the collection type, that is the collection-creation method for the collection type. Otherwise, the collection type doesn't have a collection-creation method. + +It is an error if the `CollectionBuilder` attribute does not refer to an invokable method with the expected signature. + +For a *collection_expression* with a target type `C` where the type declaration `C` has an associated collection-creation method `B.M()`, the generic type arguments from the target type are applied in order (from outermost containing type to innermost) to the collection-creation method. + +The span parameter for the collection-creation method may be explicitly marked `scoped` or `[UnscopedRef] ([§9.7.3](variables.md#973-the-scoped-modifier))`. If the parameter is implicitly or explicitly `scoped`, the compiler may allocate the storage for the span on the stack rather than the heap. + +The construction of an instance of a collection type is described in §collection-construction. + +### §collection-construction Collection construction + +The *collection_element*s of a *collection_expression* are evaluated in order, left to right. Each *collection_element* is evaluated exactly once, and any further references to the any elements refer to the results of this initial evaluation. + +A *spread_element* may be iterated before or after the subsequent elements in the *collection_expression* are evaluated. + +An unhandled exception thrown from any of the methods used during construction shall go uncaught and shall prevent further steps in the construction. + +`Length`, `Count`, and `GetEnumerator` are assumed to have no side effects. + +If the target type is a struct or class type that implements `System.Collections.IEnumerable`, and the target type does not have a collection-creation method (§declaring-a-collection-type-general), the construction of the collection instance steps are, as follows: + +- The elements are evaluated in order. Some or all elements may be evaluated during the steps below rather than before. +- The compiler may determine the known length of the collection expression by invoking countable properties ([§18.1](ranges.md#181-general)) or equivalent properties from well-known interfaces or types, on each *spread_element*’s *expression*. +- The constructor that is applicable with no arguments is invoked. +- For each *collection_element*, in order: + + - If the *collection_element* is an *expression_element*, the applicable `Add` instance or extension method is invoked with the *element_expression* as the argument. (Unlike classic collection initializer behavior ([§12.8.17.3.1](expressions.md#1281731-collection-initializers)), element evaluation and `Add` calls are not necessarily interleaved.) + - If the *collection_element* is a *spread_element* then one of the following steps is used: + + - An applicable `GetEnumerator` instance or extension method is invoked on the *spread_element*’s *expression*, and for each item from the enumerator the applicable `Add` instance or extension method is invoked on the collection instance with the item as the argument. If the enumerator implements `IDisposable`, then `Dispose` shall be called after enumeration, regardless of any exceptions. + - An applicable `AddRange` instance or extension method is invoked on the collection instance with the *spread_element*’s *expression* as the argument. + - An applicable `CopyTo` instance or extension method is invoked on the *spread_element*’s *expression* with the collection instance and `int` index as arguments. + +- During the construction steps above, an applicable `EnsureCapacity` instance or extension method may be invoked one or more times on the collection instance with an `int` capacity argument. + +If the target type is an array, a `Span` or `ReadOnlySpan`, a type with a collection-creation method, or an interface, the construction steps of the collection instance are, as follows: + +- The elements are evaluated in order. Some or all elements may be evaluated during the steps below rather than before. +- The compiler may determine the known length of the collection expression by invoking countable properties (or equivalent properties from well-known interfaces or types) on each *spread_element*’s *expression*. + +- An *initialization instance* is created as follows: + + - If the target type is an array and the collection expression has a known length, an array is allocated with the expected length. + - If the target type is a `Span` or `ReadOnlySpan`, or a type with a collection-creation method, and the collection has a known length, a `Span` or `ReadOnlySpan` with the expected length is created referring to contiguous storage. + - Otherwise, intermediate storage is allocated. + +- For each *collection_element*, in order: + + - If the *collection_element*is an *expression_element*, the initialization instance indexer is invoked to add the evaluated expression at the current index. + - If the element is a *spread_element* then one of the following is used: + + - A member of a well-known interface or type is invoked to copy items from the spread element expression to the initialization instance. + - An applicable `GetEnumerator` instance or extension method is invoked on the *spread_element*’s *expression*, and for each item from the enumerator, the initialization instance indexer is invoked to add the item at the current index. If the enumerator implements `IDisposable`, then `Dispose` shall be called after enumeration, regardless of any exceptions. + - An applicable `CopyTo` instance or extension method is invoked on the *spread_element*’s *expression* with the initialization instance and `int` index as arguments. + +- If intermediate storage was allocated for the collection, a collection instance is allocated with the actual collection length and the values from the initialization instance are copied to the collection instance, or if a span is required the compiler may use a span of the actual collection length from the intermediate storage. Otherwise, the initialization instance is the collection instance. +- If the target type has a collection-creation method, that method is invoked with the span instance. + +> *Note:* The compiler might delay adding elements to the collection (or delay iterating through *spread_element*s) until after evaluating subsequent elements. (When subsequent *spread_element*s have countable properties that would allow calculating the expected length of the collection before allocating the collection.) Conversely, the compiler might eagerly add elements to the collection (and eagerly iterate through *spread_element*s) when there is no advantage to delaying. +> +> Consider the following collection expression: +> +> ```csharp +> int[] x = [a, ..b, ..c, d]; +> ``` +> +> If *spread_element*s `b` and `c` are countable, the compiler could delay adding items from `a` and `b` until after `c` is evaluated, to allow allocating the resulting array at the expected length. After that, the compiler could eagerly add items from `c`, before evaluating `d`, as shown below. +> +> ```csharp +> var __tmp1 = a; +> var __tmp2 = b; +> var __tmp3 = c; +> var __result = new int[2 + __tmp2.Length + __tmp3.Length]; +> int __index = 0; +> __result[__index++] = __tmp1; +> foreach (var __i in __tmp2) __result[__index++] = __i; +> foreach (var __i in __tmp3) __result[__index++] = __i; +> __result[__index++] = d; +> x = __result; +> ``` +> +> *end note* diff --git a/standard/conversions.md b/standard/conversions.md index 57fea3755..4a36be694 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -455,6 +455,52 @@ Although an implicit conversion to `object` is permitted, a warning shall be iss > > *end example* +### §imp-collection-expression-conv Implicit collection expression conversions + +An implicit collection expression conversion exists from a collection expression to the following types: + +- A single-dimensional array type `T[]`, in which case, the element type is `T`. +- `System.Span` and `System.ReadOnlySpan`, in which cases, the element type is `T`. +- A type with an appropriate collection-creation method (§declaring-a-collection-type-general), in which case, the element type is the iteration type ([§13.9.5](statements.md#1395-the-foreach-statement)) determined from a `GetEnumerator` instance method or enumerable interface, not from an extension method. +- A struct or class type that implements `System.Collections.IEnumerable` where: + + - The type has an applicable ([§12.6.4.2](expressions.md#12642-applicable-function-member)) constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression. + - If the collection expression has any elements, the type has an instance or extension method `Add` where: + + - The method can be invoked with a single value argument. + - If the method is generic, the type arguments can be inferred from the collection and argument. + - The method is accessible at the location of the collection expression. + + In which case, the element type is the iteration type of the type. +- Any of the following interface types: + + - `System.Collections.Generic.IEnumerable` + - `System.Collections.Generic.IReadOnlyCollection` + - `System.Collections.Generic.IReadOnlyList` + - `System.Collections.Generic.ICollection` + - `System.Collections.Generic.IList` + + in which case, the element type is `T` + +The implicit conversion exists if the type has an element type `U` where for each element `Eᵢ` in the collection expression: + +- If `Eᵢ` is an *expression_element*, there is an implicit conversion from `Eᵢ` to `U`. +- If `Eᵢ` is a *spread_element* `..Sᵢ`, there is an implicit conversion from the *iteration type* of `Sᵢ` to `U`. + +There is no collection expression conversion from a collection expression to a multi-dimensional array type. + +Types for which there is an implicit collection expression conversion from a collection expression are the valid target types for that collection expression. + +The following additional implicit conversions exist from a collection expression: + +- To a nullable value type `T?` where there is a collection expression conversion from the collection expression to the value type `T`. The conversion is a collection expression conversion to `T` followed by an implicit nullable conversion from `T` to `T?`. + +- To a reference type `T` where there is a collection-creation method associated with `T` that returns a type `U` and there is an implicit reference conversion from `U` to `T`. The conversion is a collection expression conversion to `U` followed by an implicit reference conversion from `U` to `T`. + +- To an interface type `I` where there is a collection-creation method associated with `I` that returns a type `V` and there is an implicit boxing conversion from `V` to `I`. The conversion is a collection expression conversion to `V` followed by an implicit boxing conversion from `V` to `I`. + +When a collection expression is converted to a ref struct type, all ref safety requirements ([§9.7.2](variables.md#972-ref-safe-contexts), [§16.5.15](structs.md#16412-safe-context-constraint)) shall be met. + ## 10.3 Explicit conversions ### 10.3.1 General diff --git a/standard/expressions.md b/standard/expressions.md index 446c56be0..d382dc9f5 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -779,7 +779,7 @@ Type inference takes place in phases. Each phase will try to infer type argument #### 12.6.3.2 The first phase -For each of the method arguments `Eᵢ`, an input type inference ([§12.6.3.7](expressions.md#12637-input-type-inferences)) is made from `Eᵢ` to the corresponding parameter type `Tⱼ`. +For each of the method arguments `Eᵢ`, an input type inference ([§12.6.3.7](expressions.md#12637-input-type-inferences)) is made from `Eᵢ` to the corresponding parameter type `Tᵢ`. #### 12.6.3.3 The second phase @@ -819,8 +819,11 @@ An *unfixed* type variable `Xᵢ` *depends directly on* an *unfixed* type varia An *input type inference* is made *from* an expression `E` *to* a type `T` in the following way: -- If `E` is a tuple literal ([§12.8.6](expressions.md#1286-tuple-literals)) with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ`, an input type inference is made from `Eᵢ` to `Tₑ`. -- If `E` is an anonymous function, an *explicit parameter type inference* ([§12.6.3.9](expressions.md#12639-explicit-parameter-type-inferences)) is made *from* `E` *to* `T` +- If `E` is a *collection_expression* with elements `Eᵢ`, and `T` is a type with an element type `Tₑ` or `T` is a *nullable_value_type* `T0?` and `T0` has an element type `Tₑ`, then for each `Eᵢ`: + - If `Eᵢ` is an *expression_element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. + - If `Eᵢ` is a *spread_element* with an iteration type ([§13.9.5](statements.md#1395-the-foreach-statement)) `Sᵢ`, then a lower-bound inference([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `Sᵢ` *to* `Tₑ`. +- If `E` is a tuple expression ([§12.8.6](expressions.md#1286-tuple-expressions)) with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ`, an input type inference is made from `Eᵢ` to `Tₑ`. +- If `E` is an anonymous function and `T` is a delegate type or expression tree type, an *explicit parameter type inference* ([§12.6.3.9](expressions.md#12639-explicit-parameter-type-inferences)) is made *from* `E` *to* `T` and an *explicit return type inference* is made from `E` to `T`. - Otherwise, if `E` has a type `U` and the corresponding parameter is a value parameter ([§15.6.2.2](classes.md#15622-value-parameters)) then a *lower-bound inference* ([§12.6.3.11](expressions.md#126311-lower-bound-inferences)) is made *from* `U` *to* `T`. - Otherwise, if `E` has a type `U` and the corresponding parameter is a reference parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)), or output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)) then an *exact inference* ([§12.6.3.10](expressions.md#126310-exact-inferences)) is made *from* `U` *to* `T`. - Otherwise, if `E` has a type `U` and the corresponding parameter is an input parameter ([§15.6.2.3.2](classes.md#156232-input-parameters)) and `E` is an input argument, then an *exact inference* ([§12.6.3.10](expressions.md#126310-exact-inferences)) is made *from* `U` *to* `T`. @@ -831,8 +834,11 @@ An *input type inference* is made *from* an expression `E` *to* a type `T` in th An *output type inference* is made *from* an expression `E` *to* a type `T` in the following way: -- If `E` is a tuple literal with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ` an output type inference is made from `Eᵢ` to `Tₑ`. -- If `E` is an anonymous function with inferred return type `U` ([§12.6.3.14](expressions.md#126314-inferred-return-type)) and `T` is a delegate type or expression tree type with return type `Tₓ`, then a *lower-bound inference* ([§12.6.3.11](expressions.md#126311-lower-bound-inferences)) is made *from* `U` *to* `Tₓ`. +- If `E` is a *collection_expression* with elements `Eᵢ`, and `T` is a type with an element type `Tₑ` or `T` is a *nullable_value_type* `T0?` and `T0` has an element type `Tₑ`, then for each `Eᵢ`: + - If `Eᵢ` is an *expression_element*, then an *output type inference* is made from `Eᵢ` to `Tₑ`. + - If `Eᵢ` is a *spread_element*, no inference is made from `Eᵢ`. +- Otherwise, if `E` is a tuple expression with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ` an output type inference is made from `Eᵢ` to `Tₑ`. +- **TBD** Otherwise, if `E` is an anonymous function with inferred return type `U` ([§12.6.3.14](expressions.md#126314-inferred-return-type)) and `T` is a delegate type or expression tree type with return type `Tₓ`, then a *lower-bound inference* ([§12.6.3.11](expressions.md#126311-lower-bound-inferences)) is made *from* `U` *to* `Tₓ`. - Otherwise, if `E` is a method group and `T` is a delegate type or expression tree type with parameter types `T₁...Tᵥ` and return type `Tₓ`, and overload resolution of `E` with the types `T₁...Tᵥ` yields a single method with return type `U`, then a *lower-bound inference* is made *from* `U` *to* `Tₓ`. - If `E` is an address-of method group and `T` is a function pointer type ([§24.3.3](unsafe-code.md#2433-function-pointers)) then with parameter types `T1..Tk` and return type `Tb`, and overload resolution of `E` with the types `T1..Tk` yields a single method with return type `U`, then a *lower-bound inference* is made from `U` to `Tb`. > *Note*: This is only applicable in unsafe code. *end note* @@ -1176,15 +1182,47 @@ Given `int i = 10;`, according to [§12.6.4.2](expressions.md#12642-applicable-f Given an implicit conversion `C₁` that converts from an expression `E` to a type `T₁`, and an implicit conversion `C₂` that converts from an expression `E` to a type `T₂`, `C₁` is a ***better conversion*** than `C₂` if one of the following holds: -- `C₁` is not an anonymous function type conversion and `C₂` is an anonymous function type conversion, or -- `E` is a non-constant *interpolated_string_expression*, `C₁` is an implicit interpolated string handler conversion, `T₁` is an applicable interpolated string handler type, and `C₂` is not an implicit interpolated string handler conversion. -- `E` does not exactly match `T₂` and at least one of the following holds: - - `E` exactly matches `T₁` and `E` does not exactly match `T₂` ([§12.6.4.6](expressions.md#12646-exactly-matching-expression)) - - `C₁` is not a conditional expression conversion and `C₂` is a conditional expression conversion. - - `E` exactly matches both or neither of `T₁` and `T₂`, and `T₁` is a better conversion target than `T₂` ([§12.6.4.7](expressions.md#12647-better-conversion-target)) and either `C₁` and `C₂` are both conditional expression conversions or neither is a conditional expression conversion. - - `V` is a function pointer type `delegate*` and `U` is a function pointer type `delegate*`, and the calling convention of `V` is identical to `U`, and the refness of `Vi` is identical to `Ui`. - > *Note*: This is only applicable in unsafe code. *end note* - - `E` is a method group ([§12.2](expressions.md#122-expression-classifications)), `T₁` is compatible ([§21.4](delegates.md#214-delegate-compatibility)) with the single best method from the method group for conversion `C₁`, and `T₂` is not compatible with the single best method from the method group for conversion `C₂` +- `E` is a *collection_expression* and one of the following holds: + - `T₁` is `System.ReadOnlySpan`, and `T₂` is `System.Span`, and an implicit conversion exists from `E₁` to `E₂`. + - `T₁` is `System.ReadOnlySpan` or `System.Span`, and `T₂` is an *array_type*, or one of the following interface types implemented by an *array_type*: `System.Collections.Generic.IEnumerable`, `System.Collections.Generic.IReadOnlyCollection`, `System.Collections.Generic.IReadOnlyList`, `System.Collections.Generic.ICollection`, or `System.Collections.Generic.IList` with element type `E₂`, and an implicit conversion exists from `E₁` to `E₂`. + - `T₁` and `T₂` are not `System.ReadOnlySpan` or `System.Span`, and an implicit conversion exists from `T₁` to `T₂`. +- `E` is not a *collection_expression* and one of the following holds: +- `C1` is not a *function_type_conversion* and `C2` is a *function_type_conversion*, or +- `E` exactly matches `T₁` and `E` does not exactly match `T₂` ([§12.6.4.6](expressions.md#12646-exactly-matching-expression)) +- `C₁` is not a conditional expression conversion and `C₂` is a conditional expression conversion. +- `E` exactly matches both or neither of `T₁` and `T₂`, and `T₁` is a better conversion target than `T₂` ([§12.6.4.7](expressions.md#12647-better-conversion-target)) and either `C₁` and `C₂` are both conditional expression conversions or neither is a conditional expression conversion. + - `V` is a function pointer type `delegate*` and `U` is a function pointer type `delegate*`, and the calling convention of `V` is identical to `U`, and the refness of `Vi` is identical to `Ui`. + > *Note*: This is only applicable in unsafe code. *end note* +- `E` is a method group ([§12.2](expressions.md#122-expression-classifications)), `T₁` is compatible ([§21.4](delegates.md#214-delegate-compatibility)) with the single best method from the method group for conversion `C₁`, and `T₂` is not compatible with the single best method from the method group for conversion `C₂` + +> *Example*: The following example shows the differences with overload resolution between array initializers and collection expressions: +> +> +> ```csharp +> class C +> { +> static void Generic(Span value) { } +> static void Generic(T[] value) { } +> static void SpanDerived(Span value) { } +> static void SpanDerived(object[] value) { } +> static void ArrayDerived(Span value) { } +> static void ArrayDerived(string[] value) { } +> static void M() +> { +> // Array initializers +> Generic(new[] { "" }); // string[] +> SpanDerived(new[] { "" }); // ambiguous +> ArrayDerived(new[] { "" }); // string[] +> +> // Collection expressions +> Generic([""]); // Span +> SpanDerived([""]); // Span +> ArrayDerived([""]); // ambiguous +> } +> } +> ``` +> +> *end example* #### 12.6.4.6 Exactly matching expression @@ -1392,6 +1430,7 @@ primary_expression | pointer_member_access // unsafe code support | pointer_element_access // unsafe code support | stackalloc_expression + | collection_expression ; ``` @@ -3773,6 +3812,69 @@ These are the same transformations applied in [§6.4.3](lexical-structure.md#643 An *anonymous_method_expression* is one of two ways of defining an anonymous function. These are further described in [§12.22](expressions.md#1222-anonymous-function-expressions). +### §collection-expressions Collection expressions + +A ***collection expression*** is a `[]`-delimited, comma-separated set of zero or more *collection_element*s that together represent a collection. + +```ANTLR +collection_expression + : '[' (collection_element (',' collection_element)*)? ']' + ; + +collection_element + : expression_element + | spread_element + ; + +expression_element + : expression + ; + +spread_element + : '..' expression + ; +``` + +On its own, a *collection_expression* has no type, but, rather, it is target-typed; that is, depending on the context in which it is used, it is converted (§imp-collection-expression-conv) to the type of the target (presuming such a conversion is permitted). Any type that supports a *collection_initializer* ([§12.8.17.3.1](expressions.md#1281731-collection-initializers)) may be a target type for a *collection_expression*. A type designated with the `CollectionBuilder` attribute may also be a target type (§declaring-a-collection-type-general). + +The *expression* of a *collection_element* need not be a constant. A *collection_expression* is not a compile-time constant, even if all its *collection_element*s are. + +> *Example*: +> +> +> ```csharp +> string[] colors = ["red", "white", "blue"]; // initialization +> List counts = [10, 25, 45, 67]; +> Span list = [5.4, 3.9F, 123, 'x']; +> ReadOnlySpan items = [counts[2], 19.5, 'X', colors[1]]; +> int v = ((int[])[10, 20])[1]; // explicit target type cast +> ``` +> +> In the case of `colors`, the collection expression is converted to a `string[]` having `Length` 3. For `counts`, the collection expression is converted to a `List` having `Count` 4. For `list`, the collection expression is converted to a `Span` having `Length` 4, with the *expression*s being converted implicitly to `double`, as necessary. For `items`, the collection expression is converted to a `ReadOnlySpan` having `Length` 4, with the *expression*s being boxed. *end example* + +The *collection_expression* `[]` represents an empty collection. The conversion of such an expression results in a collection having a `Count` or `Length` of 0, with any property `isEmpty` returning `true`. + +A *spread_element* causes the set of *collection_element*s designated by *expression* to be copied to the new collection starting at the location of that *spread_element*. *expression* shall designate an entity that is enumerable using a `foreach` statement. + +> *Example*: +> +> +> ```csharp +> string[] colors1 = ["red", "white", "blue"]; +> string[] colors2 = ["black", .. colors1, "yellow"]; +> List directions = ["north", "south", "east", "west"]; +> Span words = ["March", .. colors1, "Hello",.. directions]; +> ReadOnlySpan things = [123, 10.5, .. directions]; +> +> int[] counts = [10, 20, 30]; +> List values = [1.2, 3.5]; +> Span numbers = [.. counts, .. values]; +> ``` +> +> `colors2` contains five elements: `"black"`, `"red"`, `"white"`, `"blue"`, and `"yellow"`. `words` is created from two elements of type `string` plus all the elements of a `string[]` and a `List`, in the order shown. And `things` is created from an `int`, a `double`, and all the elements of a `List`. As shown, the types of the container from which a collection is copied is immaterial. And in the case of `numbers`, the element types need not be the same. *end example* + +If the *expression* in a *spread_element* is `[]`, that *spread_element* may be ignored, as it contributes no elements. + ## 12.9 Unary operators ### 12.9.1 General diff --git a/standard/standard-library.md b/standard/standard-library.md index b2f2dcbd0..91a77399a 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -815,6 +815,16 @@ namespace System.Linq.Expressions namespace System.Runtime.CompilerServices { + [System.AttributeUsage(System.AttributeTargets.Class + | System.AttributeTargets.Interface + | System.AttributeTargets.Struct, Inherited=false)] + public sealed class CollectionBuilderAttribute : System.Attribute + { + public CollectionBuilderAttribute(Type builderType, string methodName); + public Type BuilderType { get; } + public string MethodName { get; } + } + public ref struct DefaultInterpolatedStringHandler { public DefaultInterpolatedStringHandler(int literalLength, @@ -1524,6 +1534,7 @@ The following library types are referenced in this specification. The full names - `global::System.Runtime.CompilerServices.CallerFilePathAttribute` - `global::System.Runtime.CompilerServices.CallerLineNumberAttribute` - `global::System.Runtime.CompilerServices.CallerMemberNameAttribute` +- `global::System.Runtime.CompilerServices.CollectionBuilderAttribute` - `global::System.Runtime.CompilerServices.DefaultInterpolatedStringHandler` - `global::System.Runtime.CompilerServices.FormattableStringFactory` - `global::System.Runtime.CompilerServices.ICriticalNotifyCompletion`