csharplang/proposals/index-range-changes.md

132 lines
5.6 KiB
Markdown
Raw Normal View History

2019-04-01 01:00:29 +02:00
# Index and Range Changes
## Summary
This proposes to make several changes to the
[index and range design](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md) based on
customer feedback: particularly from the CoreFX team and their experiences adding Index / Range support to .NET Core.
The change is not to the syntax but rather how the langauge maps the syntax to APIs.
## Motivation
- Abstraction penalty
- Performance
- Reduce the API burden for making existing libraries Index / Range aware
## Detailed Design
2019-04-01 04:03:26 +02:00
- Does not change the existing type logic.
2019-04-01 01:00:29 +02:00
### Indexable types
2019-04-01 04:03:26 +02:00
Any type which has an accessible getter property named `Length` or `Count` with a return type of `int` is considered
Indexable. The language can use this property to efficiently convert an `Index` expression into an `int` at the
point of the expression without relying on the implementation of receiver to do so.
Note: For simplicity going forward the proposal will use the name `Length` to represent `Count` or `Length`.
For example it allows the following simplication:
``` csharp
Span<char> span = ...;
char c = span[^1];
// Can be translated to
Span<char> span = ...;
char c = span[span.Length - 1];
```
Transforming an `Index` to an `int` at the call site significantly reduces the burden of frameworks to adopt `Index`.
Vitrually any collection type will automatically work with `Index` now as the compiler can translate it to `int` in
all cases.
Further this can improve performance by eliminating extra branching. The callee when accepting an `Index` parameter must
do both test to see if the value is from the end, `Index.IsFromEnd`, and if the value is inside the bounds of the
collection. While a small check this can be important in performance sensitive areas.
Doing the translation to `int` at the call site means the `IsFromEnd` check can often be eliminated. For example when
dealing with an `int` the compiler can pass the value through. Or in the cases where `^` is used the computation from
end can be done directly without the additional branching.
### Index target type conversion
Whenever an expression with type `Index` is used as an argument to an instance member invocation and the receiver is
Indexable then the expression will have a target type conversion to `int`. The member invocations applicable for this
conversion include methods, indexers, properties, extension methods, etc ... Only constructors are excluded as they
have no receiver.
The target type conversion will be implemented as follows:
- When the expression is `^expr` and the type is `int` it will be translated to `receiver.Length - expr`. This
arithmetic will be implemented in an unchecked fashion.
- Else it will be translated as `expr.GetOffset(receiver.Length)` where `expr` is the expression typed as `Index`.
The receiver will be spilled as appropriate to ensure the side effects of obtaining the receiver are only executed
once. For example:
``` csharp
class SideEffect {
int[] Get() {
Console.Write("Get ");
return new [] { 1, 2 , 3};
}
void Use() {
int i = Get()[^1]
Console.WriteLine(i);
}
}
```
This code will print "Get 3".
### Index and Range implementations are known
The implementations of `Index` and `Range` are considered to be known and side effect free. Much like
`ValueTuple<T1, T2>` the language can assume a standard implementation and optimize around it.
2019-04-01 01:00:29 +02:00
### Detailed Decision Point 1
## Open Issues
2019-04-01 04:03:26 +02:00
### Extension method for Indexer
2019-04-01 01:00:29 +02:00
## Considerations
### Detect Indexable based on ICollection
The inspiration for this proposal was collection initializers. Using the structure of a type to convey that it had
opted into a feature. In the case of collection initializers types can opt into the feature by implementing the
interface `IEnumerable` (non generic).
Initially this proposal required that types implement `ICollection` in order to qualify as Indexable. That though
required a number of special cases:
- `ref struct`: these cannot implement interfaces yet types like `Span<T>` are ideal for index / range support.
- `string`: does not implement `ICollection` and adding that `interface` has a large cost.
This means to support key types special casing is already needed. The special casing of `string` is less interesting
as the language does this in other areas (`foreach` lowering, constants, etc ...). The special casing of `ref struct`
is more concerning as it's special casing an entire class of types. The get labeled as Indexable if they simply have
a property named `Count` with a return type of `int`.
2019-04-01 04:03:26 +02:00
After consideration the design was normalized to say that any type which has a property `Count` / `Length` with a
return type of `int` is Indexable. That removes all special casing, even for `string` and arrays.
### Detect just Count
Detecting on the property names `Count` or `Length` does complicate the design a bit. Picking just one to standardize
though is not sufficient as it ends up excluding a large number of types:
- Use `Length`: excludes pretty much every collection in System.Collections and sub-namespaces. Those tend to derive
from `ICollection` and hence prefer `Count` over length.
- Use `Count`: excludes `string`, arrays, `Span<T>` and most `ref struct` based types
The extra complication on the initial detection of Indexable types is outweighed by it's simplification in other
aspects.
### Bind to a
2019-04-01 01:00:29 +02:00
2019-04-01 04:03:26 +02:00
### Choice of Slice as a anme
2019-04-01 01:00:29 +02:00
### Consideration 2
## Related Issues
- https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.cs
- https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md
## Design Meetings