You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Relax Add requirement for collection expression conversions to types implementing IEnumerable
Summary
The conversion rules for collection expressions were tightened at LDM-2024-01-10 to require target types that implement IEnumerable, and that do not have create method, to have:
An accessible Add instance or extension method that can be invoked with value of iteration type as the argument.
That is a breaking change for collection types where the Add method has a parameter type that is implicitly convertible but not identical to the iteration type. Those types are no longer valid targets for collection expressions. We should relax the recent Add method requirement to address the breaking change.
Additionally, there are collection types where there is no conversion between the Add method parameter type and the iteration type. Those types are valid targets for classic collection initializers but have never been valid for collection expressions. We could consider supporting those types for collection expressions as well.
Examples
There are several categories of collection types that are supported by classic collection initializers that are not supported in collection expressions.
The first two categories were supported before the recent requirement for Add, and these two categories represent the breaking change. The last category has not been supported previously with collection expressions but could be considered.
Category 1: Types that implement IEnumerable but not IEnumerable<T> and have a strongly-typed Add(T).
Category 2: Types that implement IEnumerable<T> and have a strongly-typed Add(U) where U is implicitly convertible to T.
This is a generic form of category 1.
Example: System.CommandLine.Command implements IEnumerable<Symbol> and has Add() methods for derived types of Symbol but not for Symbol.
This issue affects target types that implement IEnumerable and do not have create methods. The conversion requirements for such types were modified in LDM-2024-01-10, and those changes were implemented in 17.10p1.
In 17.8, conversion simply requires the target type to implement non-generic IEnumerable, and does not require an Add method:
An implicit collection expression conversion exists from a collection expression to the following types:
...
A struct or class type that implements System.Collections.IEnumerable.
The implicit conversion exists if the type has an iteration typeU where for each elementEᵢ in the collection expression:
If Eᵢ is an expression element, there is an implicit conversion from Eᵢ to U.
If Eᵢ is an spread elementSᵢ, there is an implicit conversion from the iteration type of Sᵢ to U.
Even though an Add method is not required for conversion, an Add method is required for construction of the collection instance. That requirement is checked after determining convertibility, and the requirement is that for each element there is an applicable Add method that can be called with that expression:
The constructor that is applicable with no arguments is invoked.
For each element in order:
If the 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, element evaluation and Add calls are not necessarily interleaved.)
If the element is a spread element then ...
In 17.10p1, conversion was updated to require the constructor and also to require an Add method callable with an argument of the iteration type. For construction, the requirements were unchanged from 17.8. The updated conversion requirement:
An implicit collection expression conversion exists from a collection expression to the following types:
...
A struct or class type that implements System.Collections.IEnumerable where:
The type has an applicable 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 applicable instance or extension method Add that can be invoked with a single argument of the iteration type, and the method is accessible at the location of the collection expression.
The implicit conversion exists if the type has an iteration typeU where for each elementEᵢ in the collection expression:
If Eᵢ is an expression element, there is an implicit conversion from Eᵢ to U.
If Eᵢ is an spread elementSᵢ, there is an implicit conversion from the iteration type of Sᵢ to U.
There were two motivations for the additional Add method requirement:
Reduce the number of IEnumerable implementations that are considered valid target types for collection expressions but which fail to bind successfully. This is important for overload resolution to avoid unnecessary ambiguities.
Align with the params collections preview feature which has the additional requirement to allow validating the params type at the method declaration rather than only at call sites.
For params collections, the requirements for the preview feature are:
The type of a parameter collection shall be one of the following valid target types for a collection expression:
...
A struct or class type that implements System.Collections.IEnumerable where:
The type has a constructor that can be invoked with no arguments, and the constructor is at least as accessible as the declaring member.
The type has an instance (not an extension) method Add that can be invoked with a single argument of
the iteration type,
and the method is at least as accessible as the declaring member.
Proposal
Relax the requirement for conversion to require an instance or extension Add method that can be invoked with a single argument, but without requirements on the method parameter type.
For construction, there are no changes.
For collection expression conversions, the proposed change:
An implicit collection expression conversion exists from a collection expression to the following types:
...
A struct or class type that implements System.Collections.IEnumerable where:
The type has an applicable 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 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.
The implicit conversion exists if the type has an iteration typeU where for each elementEᵢ in the collection expression:
If Eᵢ is an expression element, there is an implicit conversion from Eᵢ to U.
If Eᵢ is an spread elementSᵢ, there is an implicit conversion from the iteration type of Sᵢ to U.
The proposed change would resolve the breaking change in 17.10p1 and allow types in category 1 and 2 to be used as collection expression target types.
Types in category 3 would still not be valid target types, unchanged from 17.8.
For params collections, there is a corresponding proposed change:
The type of a parameter collection shall be one of the following valid target types for a collection expression:
...
A struct or class type that implements System.Collections.IEnumerable where:
The type has a constructor that can be invoked with no arguments, and the constructor is at least as accessible as the declaring member.
The type has an instance (not an extension) method Add where:
The method can be invoked with a single argument.
If the method is generic, the type arguments can be inferred from the argument.
The method is at least as accessible as the declaring member.
Alternate (extended) proposal
We could extend the proposal above to remove the requirement that each element in the collection expression is implicitly convertible to the iteration type for these types. (We would only remove this requirement for types that implement IEnumerable and do not use a create method.)
For collection expression conversions, the alternate proposed change:
An implicit collection expression conversion exists from a collection expression to the following types:
...
A struct or class type that implements System.Collections.IEnumerable where:
The type has an applicable 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 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.
The implicit conversion exists if the type has an iteration typeU where for each elementEᵢ in the collection expression:
If Eᵢ is an expression element, there is an implicit conversion from Eᵢ to U.
If Eᵢ is an spread elementSᵢ, there is an implicit conversion from the iteration type of Sᵢ to U.
The alternate proposal would allow types in category 1, 2, and 3 to be used as collection expression target types.
The alternate proposal would however have implications that would need to be understood and addressed. For example, the following would now become ambiguous without further changes.
Absent further work, this would be a breaking change. As this is a scenario we want to keep working, this would need be carefully considered and designed, and we are not proposing making this change in 17.10.
Relax
Add
requirement for collection expression conversions to types implementingIEnumerable
Summary
The conversion rules for collection expressions were tightened at LDM-2024-01-10 to require target types that implement
IEnumerable
, and that do not have create method, to have:That is a breaking change for collection types where the
Add
method has a parameter type that is implicitly convertible but not identical to the iteration type. Those types are no longer valid targets for collection expressions. We should relax the recentAdd
method requirement to address the breaking change.Additionally, there are collection types where there is no conversion between the
Add
method parameter type and the iteration type. Those types are valid targets for classic collection initializers but have never been valid for collection expressions. We could consider supporting those types for collection expressions as well.Examples
There are several categories of collection types that are supported by classic collection initializers that are not supported in collection expressions.
The first two categories were supported before the recent requirement for
Add
, and these two categories represent the breaking change. The last category has not been supported previously with collection expressions but could be considered.Category 1: Types that implement
IEnumerable
but notIEnumerable<T>
and have a strongly-typedAdd(T)
.Example:
System.Windows.Input.InputGestureCollection
implementsIEnumerable
but notIEnumerable<T>
, and has anAdd(InputGesture)
but noAdd(object)
.Category 2: Types that implement
IEnumerable<T>
and have a strongly-typedAdd(U)
whereU
is implicitly convertible toT
.This is a generic form of category 1.
Example:
System.CommandLine.Command
implementsIEnumerable<Symbol>
and hasAdd()
methods for derived types ofSymbol
but not forSymbol
.Category 3: Types that implement
IEnumerable<T>
and have a strongly-typedAdd(U)
whereU
andT
are unrelated.This category is distinctly different from the previous two categories since it has not been supported previously with collection expressions.
Example:
Xunit.TheoryData<T>
implementsIEnumerable<object[]>
, and has anAdd(T)
but noAdd(object[])
.Background
This issue affects target types that implement
IEnumerable
and do not have create methods. The conversion requirements for such types were modified in LDM-2024-01-10, and those changes were implemented in 17.10p1.In 17.8, conversion simply requires the target type to implement non-generic
IEnumerable
, and does not require anAdd
method:Even though an
Add
method is not required for conversion, anAdd
method is required for construction of the collection instance. That requirement is checked after determining convertibility, and the requirement is that for each element there is an applicableAdd
method that can be called with that expression:In 17.10p1, conversion was updated to require the constructor and also to require an
Add
method callable with an argument of the iteration type. For construction, the requirements were unchanged from 17.8. The updated conversion requirement:There were two motivations for the additional
Add
method requirement:IEnumerable
implementations that are considered valid target types for collection expressions but which fail to bind successfully. This is important for overload resolution to avoid unnecessary ambiguities.For params collections, the requirements for the preview feature are:
Proposal
Relax the requirement for conversion to require an instance or extension
Add
method that can be invoked with a single argument, but without requirements on the method parameter type.For construction, there are no changes.
For collection expression conversions, the proposed change:
The proposed change would resolve the breaking change in 17.10p1 and allow types in category 1 and 2 to be used as collection expression target types.
Types in category 3 would still not be valid target types, unchanged from 17.8.
For params collections, there is a corresponding proposed change:
Alternate (extended) proposal
We could extend the proposal above to remove the requirement that each element in the collection expression is implicitly convertible to the iteration type for these types. (We would only remove this requirement for types that implement
IEnumerable
and do not use a create method.)For collection expression conversions, the alternate proposed change:
The alternate proposal would allow types in category 1, 2, and 3 to be used as collection expression target types.
The alternate proposal would however have implications that would need to be understood and addressed. For example, the following would now become ambiguous without further changes.
Absent further work, this would be a breaking change. As this is a scenario we want to keep working, this would need be carefully considered and designed, and we are not proposing making this change in 17.10.
Meetings
Issues
The text was updated successfully, but these errors were encountered: