Skip to content

Add Support for Indexers and Ranges #605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 41 commits into
base: draft-v8
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
369ca4f
Add range operator
RexJaeschke Aug 7, 2022
d77d147
add support for indexers and ranges
RexJaeschke Aug 7, 2022
1f97b8e
add indexers and ranges
RexJaeschke Aug 7, 2022
c3047be
add indexers and ranges
RexJaeschke Aug 7, 2022
3813628
fix formatting
RexJaeschke Aug 7, 2022
54c4a55
fix formatting
RexJaeschke Aug 7, 2022
56e0322
fix formatting
RexJaeschke Aug 7, 2022
1ef3ab2
fix formatting
RexJaeschke Aug 7, 2022
c051a5d
fix formatting
RexJaeschke Aug 7, 2022
8ba0cfa
fix formatting
RexJaeschke Aug 7, 2022
18319e5
fix formatting
RexJaeschke Aug 7, 2022
7cb2cd5
add indexers and ranges
RexJaeschke Aug 7, 2022
3233609
fix md formatting
RexJaeschke Feb 6, 2023
d699f4f
fix md formatting
RexJaeschke Feb 6, 2023
0b81b47
fix md formatting
RexJaeschke Feb 6, 2023
a007699
fix md formatting
RexJaeschke Feb 6, 2023
8895e78
fix md formatting
RexJaeschke Feb 6, 2023
d8b297d
Add annotation to examples
RexJaeschke Feb 6, 2023
53d51fd
Create BitArrayPartial1.cs
RexJaeschke Feb 6, 2023
12b4632
Create BitArrayPartial2.cs
RexJaeschke Feb 6, 2023
f62b30f
Create BitArrayPartial3.cs
RexJaeschke Feb 6, 2023
cf03cbd
added annotation to examples
RexJaeschke Feb 6, 2023
9e74541
fix md formatting
RexJaeschke Feb 6, 2023
5157c57
update links
BillWagner Sep 26, 2023
39a0fdd
more section references
BillWagner Sep 26, 2023
7c67270
more section numbers
BillWagner Sep 26, 2023
fb5703c
Small bits of cleanup
RexJaeschke Aug 25, 2024
3981944
Make some small improvements
RexJaeschke Aug 25, 2024
7d53e7b
Remove non-required members
RexJaeschke Aug 25, 2024
5b596d0
Merge branch 'draft-v8' into indexers-and-ranges
RexJaeschke Aug 30, 2024
d1a8fec
fix type name ordering
RexJaeschke Aug 30, 2024
faf2eac
describe GetOffset
RexJaeschke Nov 11, 2024
0b88e74
some tweaks
RexJaeschke Nov 11, 2024
b2873d8
Merge branch 'draft-v8' into indexers-and-ranges
RexJaeschke Nov 11, 2024
cca7304
Merge branch 'draft-v8' into indexers-and-ranges
RexJaeschke Feb 4, 2025
cd2a008
Add new types to C.5
RexJaeschke Feb 4, 2025
1877160
Merge branch 'dotnet:draft-v8' into indexers-and-ranges
RexJaeschke Feb 15, 2025
be2d311
Add constraint for Index in an object initializer
RexJaeschke Feb 15, 2025
dd7764e
Update standard/expressions.md
jskeet Feb 19, 2025
f651978
Update standard/expressions.md
jskeet Feb 19, 2025
16818f0
Update standard/expressions.md
jskeet Feb 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions standard/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3299,6 +3299,8 @@ In a *ref_property_body* an expression body consisting of `=>` followed by `ref`

When a property declaration includes an `extern` modifier, the property is said to be an ***external property***. Because an external property declaration provides no actual implementation, each of the *accessor_body*s in its *accessor_declarations* shall be a semicolon.

A type is ***countable*** if it has an instance property named `Length` or `Count` with an accessible `get` accessor ([§15.7.3]( classes.md#1573-accessors)) and a return type of `int`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm…

struct Plank
{
   int Length { get; }      // all measurements in mm
   int Width { get; }
   int Thickness { get; }
}

A single plank is countable (in the sense intended)?

Maybe:

Suggested change
A type is ***countable*** if it has an instance property named `Length` or `Count` with an accessible `get` accessor ([§15.7.3]( classes.md#1573-accessors)) and a return type of `int`.
A collection type is ***countable*** if it has an instance property named `Length` or `Count` with an accessible `get` accessor ([§15.7.3]( classes.md#1573-accessors)) and a return type of `int`.

with a x-ref to wherever collection gets defined (see #1250).

Note: Qualifying, “collection type”, also avoids integers not be countable – which could otherwise confuse the numerate 😉

Note: This doesn’t address “In case both Length and Count are present, Length will be preferred.” found here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While having general terms is good this particular term currently only exists to be referenced in the new clauses “Implicit Index support” and ”Range Index support”. It would be better to move this to down to those two clauses, possibly even removing it as defined term altogether.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to the general clause of indexable sequences, where we're introducing other terms that are only relevant to that. (This doesn't address the plank example, but at least suggests where countable types are relevant.)


### 15.7.2 Static and instance properties

When a property declaration includes a `static` modifier, the property is said to be a ***static property***. When no `static` modifier is present, the property is said to be an ***instance property***.
Expand Down Expand Up @@ -5542,3 +5544,129 @@ When the body of the async function terminates, the return task is moved out of
If the return type of the async function is `void`, evaluation differs from the above in the following way: Because no task is returned, the function instead communicates completion and exceptions to the current thread’s ***synchronization context***. The exact definition of synchronization context is implementation-dependent, but is a representation of “where” the current thread is running. The synchronization context is notified when evaluation of a `void`-returning async function commences, completes successfully, or causes an uncaught exception to be thrown.

This allows the context to keep track of how many `void`-returning async functions are running under it, and to decide how to propagate exceptions coming out of them.

## §indexable-sequence Indexable sequences
Copy link
Contributor

@Nigel-Ecma Nigel-Ecma Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clause & its subclauses might be better after §15.9 Indexers, or as a subclause of §15.9.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Punt on this until the rest is ready.


### §indexable-sequence-general General

An ***indexable sequence*** is an ordered set of zero or more elements having the same type. Any given element can be accessed via an index, and a contiguous subset of elements—referred to as a ***slice***—can be denoted via a range.
Copy link
Contributor

@Nigel-Ecma Nigel-Ecma Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ref: #1250 a Proposal to Define Some Container-Related Terms

I think this definition of indexable sequence (or collection) is not right.

#1250 has a renaming and different definition:

An indexable collection is a collection in which any given element can be accessed via an index, and a contiguous subset of elements—referred to as a slice—can be denoted via a range.

#1250 also defines collection:

A collection is an ordered set of zero or more collection elements that can be used in a polymorphic way. Often, the elements are of the same data type such as int or string. Sometimes the items derive from a common type; even deriving from the most general type, object.

I”m not sure where the “polymorphic” in the collection definition comes from, but it does correctly drop the “same type” of indexable sequence.

The term “ordered set” allows the reading that it is the elements that are ordered (as in [partially] ordered sets and SortedSet<T>), which they need not be. It is the indicies that are ordered… usually.

System.Index indicies by themselves are actually not ordered and the type does not implement IComparable<Index> just IEquatable<Index>. They are ordered only w.r.t a particular collection, which has some Length, this is because indicies can be end-relative.

Maybe:

Suggested change
An ***indexable sequence*** is an ordered set of zero or more elements having the same type. Any given element can be accessed via an index, and a contiguous subset of elements—referred to as a ***slice***—can be denoted via a range.
An ***indexable sequence*** is a set of zero or more elements where any given element can be accessed via an index.
A ***range*** is a contiguous subset of ordered indices, and the subset of elements accessed via the indices in a range is referred to as a ***slice***.

Or something like that…

This is similar to the definition in #1250, but without the underlying definition of collection (for now…).

The definition of range here doesn’t address that a C# a..b Range does not include b, or that a C# Range may not be a contiguous set of ordered indices (consider 1..^Int.MaxValue which is a valid Range to construct but useless to use!).

So there is a disparity between the term “range” and C#’s type Range– that might be confusing…

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest either "sequence" or "collection" instead of "set" as well. Likewise "subsequence" instead of "subset"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Replacement comment, I hit add too quickly]

@jskeet dropping set is a good idea.

But are we taking about sequences, which by (English language) definition are ordered, or collections which are not?

Do we need to make it clear that any ordering here comes from the index type being ordered, not the elements?

Indices do not need to be ordered per se, but a range of indices implies ordered indices, and indexable sequence/collection is being defined to be used by ranges.

Which gives us:

Suggested change
An ***indexable sequence*** is an ordered set of zero or more elements having the same type. Any given element can be accessed via an index, and a contiguous subset of elements—referred to as a ***slice***—can be denoted via a range.
An ***indexable sequence*** is a sequence of zero or more elements where any given element can be accessed via an ordered index.
A ***range*** is a contiguous subset of ordered indices.
A ***slice** is the indexable (sub)sequence of elements selected in index order from an indexable sequence using the indices in a range.

Too detailed/convoluted?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question under discussion: does Dictionary<TKey, TValue> count as an indexable sequence? (Or rather, do we want it to?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting types:

  • int
  • System.Index
  • long (just for arrays)

We may want to limit this to integer-based indexes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a Dictionary<int, string>?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also had some discussion about what "ordered index" means (especially when System.Index isn't inherently ordered).


An index is represented by a read-only variable of the value type `System.Index`. A range is represented by a read-only variable of value type `System.Range`, which contains a start and end index. A slice of an array is represented by a (possibly empty) array. The representation of a slice of a user-defined type is determined by the implementer of that type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Should this be a countable type?

Suggested change
An index is represented by a read-only variable of the value type `System.Index`. A range is represented by a read-only variable of value type `System.Range`, which contains a start and end index. A slice of an array is represented by a (possibly empty) array. The representation of a slice of a user-defined type is determined by the implementer of that type.
An index is represented by a read-only variable of the value type `System.Index`. A range is represented by a read-only variable of value type `System.Range`, which contains a start and end index. A slice of an array is represented by a (possibly empty) array. The representation of a slice of a countable type that is not an array type is determined by the implementer of that type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does a slice have to be of a countable type? You can write an uncountable type with an indexer accepting a range, and make it fail at execution time if either end of the range is "from the end".

I agree it will usually be of a countable type, just trying to explore...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jskeet – In C# terms a slice made from a Range has to be of a countable type as Range is built upon Index, and Index requires a length for it to be converted into an integer offset…However a non-countable type could provide the same underlying Slice method…

Looking at your query made me think how careful we have to be with terms here.

An index is represented by a read-only variable of the value type System.Index.

No, an indexer takes an index as its argument, which does not need to be of type Index, or even int. So “…index is represented by…” is wrong, we’re using “index” to both mean a value of any type used as an index and to mean the (int, bool) pair embodied by System.Index

I think we might yet need a lot of careful wordsmithing. Drat!


For an indexable sequence of length *N*, elements can be accessed using indexes 0 through *N-1*, which are relative to the start. Elements can also be accessed relative to the end via the `^` index-from-end operator (§index-from-end-operator). `^0` denotes the (non-existent) element just beyond the end.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could specify here the range of valid values that may be provided with the ^ operator, similar to the "0 through N-1" text earlier on the line. In this case it would be "1 through N."

Also, nit:

Suggested change
For an indexable sequence of length *N*, elements can be accessed using indexes 0 through *N-1*, which are relative to the start. Elements can also be accessed relative to the end via the `^` index-from-end operator (§index-from-end-operator). `^0` denotes the (non-existent) element just beyond the end.
For an indexable sequence of length *N*, elements can be accessed using indexes 0 through *N-1*, which are relative to the start. Elements can also be accessed relative to the end via the `^` index-from-end operator (§index-from-end-operator). `^0` denotes the (non-existent) element just beyond the end.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jnm2 – 0 through N for ^ to allow for ^0? Otherwise 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definition of indexable sequence, both original and revised versions, do not fix the type of the index. Here the type is fixed to being an integer number (it does not state the actual type).


A slice can be obtained using the `..` range operator (§range-operator). A range of the form `s..e` starts at element `s` and ends with the element immediately prior to element `e`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could mention here that either or both ends may be omitted, each referring then to its appropriate extremity within the available range.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine as is. The other forms of .. are in the linked section.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, a slice might be obtained using a range – or it might fail, a range can be constructed using the range operator…

TBD: Wordsmith a suggestion

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TBD from the preceding comment:

Suggested change
A slice can be obtained using the `..` range operator (§range-operator). A range of the form `s..e` starts at element `s` and ends with the element immediately prior to element `e`.
A `Range` value can be obtained using the `..` range operator (§range-operator). An expression of the form `s..e` produces a `Range` value which starts at index`s` and ends with the index immediately prior to index `e`.
A slice of an indexable collection can be obtained by indexing the collection using a `Range` value.


All single-dimensional and jagged arrays ([§17.1](arrays.md#171-general)) are indexable sequences; multi-dimensional arrays are not! The use of indexes and ranges with arrays is described in [§12.8.11.2](expressions.md#128112-array-access).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it typical in the spec to mention jagged arrays as though they were separate from single-dimensional arrays? I had thought of jagged arrays as a special case of single-dimensional arrays.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might consider it a note:

Suggested change
All single-dimensional and jagged arrays ([§17.1](arrays.md#171-general)) are indexable sequences; multi-dimensional arrays are not! The use of indexes and ranges with arrays is described in [§12.8.11.2](expressions.md#128112-array-access).
All single-dimensional and jagged arrays ([§17.1](arrays.md#171-general)) are indexable sequences; multi-dimensional arrays are not! The use of indexes and ranges with arrays is described in [§12.8.11.2](expressions.md#128112-array-access).
> *Note*: Jagged arrays ([§17.1](arrays.md#171-general)) are arrays of single-dimensional arrays, and are therefore indexable. *end note*.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @jnm2 here – C# doesn’t have jagged arrays per se, it supports single-dimensional arrays where the element type can be an array which:

can be used to represent “jagged arrays”. [§17.1]

I would just drop the reference (and the !), x-ref fixed as per @BillWagner comment below:

Suggested change
All single-dimensional and jagged arrays ([§17.1](arrays.md#171-general)) are indexable sequences; multi-dimensional arrays are not! The use of indexes and ranges with arrays is described in [§12.8.11.2](expressions.md#128112-array-access).
All single-dimensional are indexable sequences; multi-dimensional arrays are not. The use of indexes and ranges with arrays is described in [§12.8.12.2](expressions.md#128122-array-access).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"All single-dimensional are indexable sequences" - this needs to be "All single-dimensional arrays are indexable sequences"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array access is now 12.8.12.2:

Suggested change
All single-dimensional and jagged arrays ([§17.1](arrays.md#171-general)) are indexable sequences; multi-dimensional arrays are not! The use of indexes and ranges with arrays is described in [§12.8.11.2](expressions.md#128112-array-access).
All single-dimensional and jagged arrays ([§17.1](arrays.md#171-general)) are indexable sequences; multi-dimensional arrays are not! The use of indexes and ranges with arrays is described in [§12.8.12.2](expressions.md#128122-array-access).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also say that these arrays are countable as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jskeet – I’ve now added that to the suggested definition of slice.


An object of type `string` is an indexable sequence.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And countable? (I won't keep adding this...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jskeet – Once we’ve the definition of indexable narrowed down to what is needed for this feature then countability (if it still exists as a concept) will be implied as using an Index relies on it.


A user-defined type can provide explicit support for indexer access ([§12.8.11.3](expressions.md#128113-indexer-access)) using `System.Index` and `System.Range`. (See §indexable-sequence-expl-support-for-index and §indexable-sequence-expl-support-for-range.) If various criteria are met, an existing user-defined type that does *not* have such explicit support, shall have provided for it by the implementation implicit support for such indexer and range access. (See §indexable-sequence-impl-support-for-index and §indexable-sequence-impl-support-for-range.) In both cases, the type is recognized as being an indexable sequence type.
Copy link
Contributor

@jnm2 jnm2 Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One sentence felt hard for me to follow. Does this version achieve the same goals you're looking to achieve?

Suggested change
A user-defined type can provide explicit support for indexer access ([§12.8.11.3](expressions.md#128113-indexer-access)) using `System.Index` and `System.Range`. (See §indexable-sequence-expl-support-for-index and §indexable-sequence-expl-support-for-range.) If various criteria are met, an existing user-defined type that does *not* have such explicit support, shall have provided for it by the implementation implicit support for such indexer and range access. (See §indexable-sequence-impl-support-for-index and §indexable-sequence-impl-support-for-range.) In both cases, the type is recognized as being an indexable sequence type.
A user-defined type can provide explicit support for indexer access ([§12.8.11.3](expressions.md#128113-indexer-access)) using `System.Index` and `System.Range`. (See §indexable-sequence-expl-support-for-index and §indexable-sequence-expl-support-for-range.) If various criteria are met, the implementation shall provide the same support for such indexer and range access on an existing user-defined type that does *not* have such explicit support. (See §indexable-sequence-impl-support-for-index and §indexable-sequence-impl-support-for-range.) In both cases, the type is recognized as being an indexable sequence type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like Joseph's wording here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience updating @jnm2’s suggestion with @BillWagner x-ref update:

Suggested change
A user-defined type can provide explicit support for indexer access ([§12.8.11.3](expressions.md#128113-indexer-access)) using `System.Index` and `System.Range`. (See §indexable-sequence-expl-support-for-index and §indexable-sequence-expl-support-for-range.) If various criteria are met, an existing user-defined type that does *not* have such explicit support, shall have provided for it by the implementation implicit support for such indexer and range access. (See §indexable-sequence-impl-support-for-index and §indexable-sequence-impl-support-for-range.) In both cases, the type is recognized as being an indexable sequence type.
A user-defined type can provide explicit support for indexer access ([§12.8.1123](expressions.md#128123-indexer-access)) using `System.Index` and `System.Range`. (See §indexable-sequence-expl-support-for-index and §indexable-sequence-expl-support-for-range.) If various criteria are met, the implementation shall provide the same support for such indexer and range access on an existing user-defined type that does *not* have such explicit support. (See §indexable-sequence-impl-support-for-index and §indexable-sequence-impl-support-for-range.) In both cases, the type is recognized as being an indexable sequence type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indexer access moved:

Suggested change
A user-defined type can provide explicit support for indexer access ([§12.8.11.3](expressions.md#128113-indexer-access)) using `System.Index` and `System.Range`. (See §indexable-sequence-expl-support-for-index and §indexable-sequence-expl-support-for-range.) If various criteria are met, an existing user-defined type that does *not* have such explicit support, shall have provided for it by the implementation implicit support for such indexer and range access. (See §indexable-sequence-impl-support-for-index and §indexable-sequence-impl-support-for-range.) In both cases, the type is recognized as being an indexable sequence type.
A user-defined type can provide explicit support for indexer access ([§12.8.12.3](expressions.md#128123-indexer-access)) using `System.Index` and `System.Range`. (See §indexable-sequence-expl-support-for-index and §indexable-sequence-expl-support-for-range.) If various criteria are met, an existing user-defined type that does *not* have such explicit support, shall have provided for it by the implementation implicit support for such indexer and range access. (See §indexable-sequence-impl-support-for-index and §indexable-sequence-impl-support-for-range.) In both cases, the type is recognized as being an indexable sequence type.


### §indexable-sequence-support-for-index Providing support for Index
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For both this section and the corresponding ranges section, I propose we rework them to introduce a pattern based description, following the format used for async return types, LINQ, and so on.

The concept is that a countable sequence has a certain pattern for indexer, Count, Length, etc.

A type can provide support for ranges and index operations by implementing a pattern: Indexer that takes System.Index or in System.Index etc. Slice method that takes a System.Range. Then describe ref variants.

Finally, for arrays and other types the compiler recognizes as countable, the compiler can synthesize the lowering of the indexer operations.


#### §indexable-sequence-expl-support-for-index Explicit Index support

A type having an instance indexer taking a single argument of type `System.Index`, or a first argument of that type followed by optional arguments, may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be off on a tangent here, and I'm happy to get a better understanding of what the aims are in this sentence, but here goes 😁

If optional arguments are mentioned, is params implicitly considered an optional argument, and if not, should it be mentioned alongside optional arguments here?

Also, the Roslyn compiler is happy with this code, which does not have an argument of type System.Index:

using System;

var expr = new C();

_ = expr[^2];
_ = expr is [];

class C
{
    public object this[params Index[] x] => throw null;

    public int Count => throw null;
}

In addition, due to implicit conversions, the type need not be System.Index so long as overload resolution succeeds:

using System;

var expr = new C();

_ = expr[^2];
_ = expr is [];

class C
{
    public object this[params CustomIndex[] x] => throw null;

    public int Count => throw null;
}

class CustomIndex
{
    public static implicit operator CustomIndex(Index i) => throw null;
}

The compiler is also happy with the indexer having a dynamic parameter, etc.

We also didn't mention refness of the parameter (in is allowed, but ref and out are not), etc.

I've observed the compiler team defining this general concept in terms of being able to bind arguments to an applicable overload. If we defined things the same way in the spec somewhere, we could refer to that definition. Thereby maybe we could sidestep all the gotchas around repeating ourselves exhaustively, as to what allows binding to succeed for these features.

There are other instances below to which this would also apply.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the change be this simple:

Suggested change
A type having an instance indexer taking a single argument of type `System.Index`, or a first argument of that type followed by optional arguments, may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access).
A type having an instance indexer taking a single argument with an implicit conversion to `System.Index`, or a first argument with an implicit conversion to `System.Index` followed by optional arguments, may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be off on a tangent here, and I'm happy to get a better understanding of what the aims are in this sentence, but here goes 😁

Tangents are welcome! A core part of the task is looking into all the nooks’n’crannies that might not be covered in the source material.

If optional arguments are mentioned, is params implicitly considered an optional argument, and if not, should it be mentioned alongside optional arguments here?

Also, the Roslyn compiler is happy with this code, which does not have an argument of type System.Index:

How an indexer access is resolved to a particular indexer is already covered in §12.8.11.3 Indexer access which in turn relies on §12.6.4 Overload resolution – between them these cover such things as optional parameters, params and implicit conversions. So this para is, incompletely as you have identified, echoing part of the text it is actually referencing.

So rather specifying indexers that have an argument of type Index, we need to specify in terms of indexers that accept/can be applied to a single argument of type Index and then refer to §12.8.11.3 which covers all the cases.

Suggested rewording:

Suggested change
A type having an instance indexer taking a single argument of type `System.Index`, or a first argument of that type followed by optional arguments, may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access).
A type having an instance indexer which can be applied to a single argument of type `System.Index` may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access).

Once the para is reduced to this it is clear it is stating the blindly obvious – before System.Index you could write an indexer taking any type of argument you wish, after you still can. Do we really need this at all?

Following below there may be further suggestions changing “take” to ”accept”/”apply” – this and those need to be accepted/rejected as a whole.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from anything else: I think these should refer to parameters, not arguments. There's no such thing as an optional argument, only an optional parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jskeet – I think the use of “argument” is correct in my suggestion?


> *Example*: In [§15.9](classes.md#159-indexers), there is an example defining type `BitArray`, which stores bits in an array of `int`. Individual bits are accessed for read/write via an `int` indexer. Adding an `Index` indexer that simply interprets the `Index` argument as an `int`, is simple:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the preceding para has, hopefully, just stated the obvious – that adding another indexer is nothing new, I question whether this really needs an example which visually separates the previous para from the next clause that builds upon it.

However I accept this might not be a universal view! 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If nothing else, we can reduce the clutter using expression bodies:

partial class BitArray
{
    public bool this[Index idx]
    {
        get => this[idx.GetOffset(Length)];   // use the [int] indexer
        set => this[idx.GetOffset(Length)] = value;  // use the [int] indexer
    } 
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jskeet – that’s an improvement 👍

>
> <!-- Example: {template:"standalone-console", name:"ExplicitIndexSupport", additionalFiles:["BitArrayPartial1.cs"], expectedOutput:["ba1[0] = True", "ba1[98] = False", "ba1[Index 0] = True", "ba1[^1] = True"]} -->
> ```csharp
> partial class BitArray
> {
> public bool this[Index idx]
> {
> get
> {
> return this[idx.GetOffset(Length)]; // use the [int] indexer
> }
> set
> {
> this[idx.GetOffset(Length)] = value; // use the [int] indexer
> }
> }
> }
> ```
>
> *end example*

#### §indexable-sequence-impl-support-for-index Implicit Index support

An implementation shall behave as if it provides a non-virtual instance indexer member with a single parameter of type `System.Index` for any type that meets the following criteria:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be clarified. The implementation only behaves this way in regard to the call site which calls this synthesized indexer. It does not, for instance, behave as if it provides this indexer within the class definition itself when the class is being compiled:

Console.WriteLine(typeof(C).GetProperties().Length); // 2

class C
{
    public int this[int index] { get => throw null; set => throw null; }
    public int Count => throw null;
}

Same again below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jnm2 – Hmm, I read “shall behave as if it provides” (emphasis added) as clearly stating what you’d like clarified – but folk do read things differently. Can you suggest some wording?

BTW I would expect that an implementation could choose to add an actual indexer to the type should it wish and be compliant, so if folk agree and don’t think this wording allows that then some wordsmithing will be required.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd personally not expect it to add an actual indexer to the type, as that could then be detected as a breaking change later if someone adds an actual indexer with a different parameter name.

But I do think it could potentially be clarified in terms of what's doing the "providing", e.g.

For any type T that meets the following criteria, an implementation shall behave as if T provides a non-virtual instance indexer member with a single parameter of type System.Index


- The type is countable ([§15.7.1](classes.md#1571-general)).
- The type has an accessible instance indexer taking an argument of type `int` as its only argument.
Copy link
Contributor

@jnm2 jnm2 Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In contrast to another comment thread, this is an example where "able to bind (int)" is not the only requirement, since subsequent optional parameters do throw it off. 👍

Is parameter refness important to mention? in int is not supported, for instance.

Copy link
Contributor

@Nigel-Ecma Nigel-Ecma Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Replaced] (My fears were justified)

For the refness question, maybe:

The type has an accessible instance indexer taking a single parameter of type int without any associated parameter_mode_modifier.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I think we should talk about the declared parameters, rather than "taking an argument".

- The type does not have an accessible instance indexer taking a `System.Index` as its only argument, or as its first argument with the remaining arguments being optional.
Comment on lines +5596 to +5600
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think by this list, the following should not error, but it does:

var dict = new X();
dict[^1] = "";

class X : Dictionary<int, string>
{
}


The provided instance indexer shall have the same get and set members with matching accessibility as the `int` indexer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'range' version of this called out that the return type was the same, including 'ref'. That is true here too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand what's meant by "the same get and set members" here in the first place... My guess is that this is to do with "if the int-based indexer is readable, then so is the provided Index-based indexer; likewise is the int-based indexer is writable, then so is the provided Index-based indexer" but I could be wrong.


The provided instance indexer shall take the given `System.Index` and use that to call the instance indexer taking an `int`. If both the `Length` and `Count` properties exist and are accessible, `Length` is used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converting descriptive to specification language:

Suggested change
The provided instance indexer shall take the given `System.Index` and use that to call the instance indexer taking an `int`. If both the `Length` and `Count` properties exist and are accessible, `Length` is used.
The provided instance indexer shall take the given `System.Index` and use that to call the instance indexer taking an `int`. If both the `Length` and `Count` properties exist and are accessible, `Length` shall be used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further to my last comment, there is no context here as to why either Length or Count should be used at all – there is no mention of GetOffset is there is in expressions.md line 2242. And as commented on the latter, surely we don't need this stuff in two places?


> *Note*: See §indexable-sequence-expl-support-for-index for an example of an explicitly provided `Index` indexer. If that were not defined, its equivalent would be provided by the implementation. *end note*

### §indexable-sequence-support-for-range Providing support for Range

#### §indexable-sequence-expl-support-for-range Explicit Range support

A type having an instance indexer taking a single argument of type `System.Range`, or a first argument of that type followed by optional arguments, may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

section change:

Suggested change
A type having an instance indexer taking a single argument of type `System.Range`, or a first argument of that type followed by optional arguments, may be indexed as described by [§12.8.11.3](expressions.md#128113-indexer-access).
A type having an instance indexer taking a single argument of type `System.Range`, or a first argument of that type followed by optional arguments, may be indexed as described by [§12.8.12.3](expressions.md#128123-indexer-access).


> *Example*: In [§15.9](classes.md#159-indexers), there is an example defining type `BitArray`, which stores bits in an array of `int`. Adding a `Range` indexer that returns a `BitArray` representing the bit slice designated by the Range, is simple:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Simple" seems to be a subjective term. What would the goal be to communicate here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jnm2 – maybe “straightforward” which doesn’t carry the same risk of offense as “simple”, or may be:

Suggested change
> *Example*: In [§15.9](classes.md#159-indexers), there is an example defining type `BitArray`, which stores bits in an array of `int`. Adding a `Range` indexer that returns a `BitArray` representing the bit slice designated by the Range, is simple:
> *Example*: In [§15.9](classes.md#159-indexers), there is an example defining type `BitArray`, which stores bits in an array of `int`. Adding a `Range` indexer that returns a `BitArray` representing the bit slice designated by the `Range` may be achieved as follows:

Wordsmith away folks!

>
> <!-- Example: {template:"standalone-console", name:"ExplicitRangeSupport", additionalFiles:["BitArrayPartial2.cs"], expectedOutput:["ba = >10011<","BitArray is >10011<","BitArray is >10011<","BitArray is >1<","BitArray is >1<","BitArray is ><"]} -->
> ```csharp
> partial class BitArray
> {
> public BitArray this[Range range] // note the return type
> {
> get
> {
> int startIdx = range.Start.GetOffset(Length);
> int endIdx = range.End.GetOffset(Length);
> int rangeLength = endIdx - startIdx;
> BitArray newBitArray = new BitArray(rangeLength);
> for (int i = startIdx; i < endIdx; ++i)
> {
> newBitArray[i - startIdx] = this[i];
> }
> return newBitArray;
> }
> }
> }
> ```
>
> *end example*

#### §indexable-sequence-impl-support-for-range Implicit Range support

An implementation shall behave as if it provides a non-virtual instance indexer member with a single parameter of type `System.Range` for any type that meets the following criteria:

- The type is countable ([§15.7.1](classes.md#1571-general)).
- The type has an accessible instance method named `Slice` taking two arguments of type `int` as the only arguments. For type `string`, the method `Substring` is used instead of `Slice`.
> *Note*: As specified in [§12.8.11.2](expressions.md#128112-array-access), for array access, the method `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray` is used instead of `Slice`. *end note*
Comment on lines +5645 to +5646
Copy link
Contributor

@jnm2 jnm2 Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is listed as a note, and arrays are left out of the list on the previous line. It seems like arrays should be given equal special-case status as strings. These are the two types that are the exception to needing a method named Slice.

If the Roslyn compiler can't find the method RuntimeHelpers.GetSubArray, it still behaves as if it provides the range indexer, and then fails at a later stage with "CS0656 Missing compiler required member 'System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray'." So the presence of this method is not a determining factor for whether the Roslyn implementation behaves as if it provides the Range indexer.

I didn't check what happens if System.String doesn't have a Substring method because that would take longer to test, but perhaps the logic is this:

- The type is `string`, an array type, or has an accessible instance method named `Slice` [...].

Then, mentions of the Substring and GetSubArray methods could be notes informing readers what will happen in those two special cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a different method is being used for arrays then it needs to be specified in normative text, not as an informative note.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we need to think about whether GetSubArray is required, or whether it would be okay for an implementation to actually provide a method called Slice instead...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

section change:

Suggested change
> *Note*: As specified in [§12.8.11.2](expressions.md#128112-array-access), for array access, the method `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray` is used instead of `Slice`. *end note*
> *Note*: As specified in [§12.8.12.2](expressions.md#128122-array-access), for array access, the method `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray` is used instead of `Slice`. *end note*

- The type does not have an accessible instance indexer taking a `System.Range` as its only argument, or as its first argument with the remaining arguments being optional.

The provided instance indexer shall have the same accessibility and return type, including `ref` if present, as `Slice`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should ref readonly be mentioned too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ref_kind, I suspect it just predates this?

Suggested change
The provided instance indexer shall have the same accessibility and return type, including `ref` if present, as `Slice`.
The provided instance indexer shall have the same accessibility and return type, including any *ref_kind* if present, as `Slice`.


When the type is indexed with a `System.Range`, the provided instance indexer shall take the given range and pass its start index and length as `int`s to `Slice` (or in the case of `string`, to method `Substring`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence doesn't hold for arrays.


> *Note*: See §indexable-sequence-expl-support-for-range for an example of an explicitly provided `Range` indexer. If that were not defined, its equivalent would be provided by the implementation, except that the provided indexer would call `Slice` to create and copy the slice. For type `BitArray`, `Slice` might be defined, as follows:
>
> <!-- Example: {template:"standalone-console", name:"ImplicitRangeSupport", additionalFiles:["BitArrayPartial3.cs"], expectedOutput:["ba = >10011<","BitArray is >10011<","BitArray is >10011<","BitArray is >1<","BitArray is >1<","BitArray is ><"]} -->
> ```csharp
> partial class BitArray
> {
> public BitArray Slice(int startIdx, int rangeLength)
> {
> int endIdx = startIdx + rangeLength;
> BitArray newBitArray = new BitArray(rangeLength);
> for (int i = startIdx; i < endIdx; ++i)
> {
> newBitArray[i - startIdx] = this[i];
> }
> return newBitArray;
> }
> }
> ```
>
> *end note*
Loading
Loading