# Extension indexers

[!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)]

Champion issue: https://github.com/dotnet/csharplang/issues/9856  

## Declaration

### Grammar

Extension indexers are added to the set of permitted members inside an extension
declaration by extending the grammar as follows (relative to
[proposals/csharp-14.0/extensions.md](proposals/csharp-14.0/extensions.md#declaration)):

```antlr
extension_member_declaration
        : method_declaration
        | property_declaration
        | indexer_declaration // new
        | operator_declaration
        ;
```

Like ordinary indexers, extension indexers have no identifier and are identified
by their parameter list. Extension indexers may use the full set of features that
ordinary indexers support today (accessor bodies, expression-bodied members,
ref-returning accessors, `scoped` parameters, attributes, etc.).

Because indexers are always instance members, an extension block that declares
an indexer must provide a named receiver parameter.  

The existing restrictions on extension members continue to apply: indexers inside an
extension declaration cannot specify `abstract`, `virtual`, `override`, `new`,
`sealed`, `partial`, `protected` (or any of the related accessibility modifiers),
or `init` accessors.

```csharp
public static class BitExtensions
{
    extension(int i)
    {
        public bool this[int index]
        {
            get => ...;
        }
    }
}
```

All rules from the C# standard that apply to ordinary indexers apply to extension indexers,
but extension members do not have an implicit or explicit `this`.

The existing extension member inferrability rule still applies: For each non-method extension member, 
all the type parameters of its extension block must be used in the combined set of parameters from the extension and the member.

### `IndexerName` attribute

`IndexerNameAttribute` may be applied to an extension indexer. The attribute is
not emitted in metadata, but its value affects conflicts between members,
it determines the name of the property and accessors in metadata, 
and is used when emitting `[DefaultMemberAttribute]` (see [Metadata](#metadata)).

## Consumption

### Indexer access

The rules in [Indexer access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128124-indexer-access)
are updated: if the normal processing of the indexer access finds no applicable indexer,
an attempt is made to process the construct as an extension indexer access.

1. Attempt to bind using the instance indexers declared (or inherited) on
    the receiver type. If an applicable candidate is found, overload resolution
    selects among those instance members as today and stops.
2. Attempt to bind using the implicit instance indexers declared (or inherited) on
    the receiver type. If an applicable candidate is found, overload resolution
    selects among those instance members as today and stops.
3. Attempt to bind as an extension indexer access.
    If this process finds an applicable candidate, overload resolution selects
    among those extension members as described below and stops.
4. Attempt to bind as an extension implicit indexer access.
    If this process finds an applicable candidate, overload resolution selects
    among those extension members as described below and stops.

Note: the element access section handles the case where an argument has type `dynamic`,
so it never gets processed as an indexer access.

#### Extension indexer access

Extension members, including extension indexers, are never considered when the
receiver is a **base_access** expression.

Note: we only process an **element_access** as an indexer access if the receiver
is a variable or value, so extension indexers are never considered when the
receiver is a type.

Given an **element_access** `E[A]`, the objective is to identify an extension indexer.

A candidate extension indexer is ***applicable*** with respect to receiver `E` and argument list `A`
if an expanded signature, comprised of the type parameters of the extension block and
a parameter list combining the extension parameter with the indexer's parameters, is applicable
with respect to an argument list combining the receiver `E` with the argument list `A`.

We reuse the extension method scope-walk: we traverse the same scopes consulted for
extension method invocation, including the current and enclosing lexical scopes
and `using` namespace or `using static` imports.

Considering each scope in turn:
- Extension blocks in non-generic static class declarations in the current scope are considered.
- The indexers in those extension blocks comprise the candidate set.
- Candidates that are not accessible are removed from the set.
- Candidates that are not applicable (as defined above) are removed from the set.
- If the resulting set of candidate indexers is empty, then we proceed to the next scope,
  or fail to resolve an extension indexer access if we reached the last scope
  (we'll continue on to attempt to resolve as an extension implicit indexer in that case).
- Otherwise, overload resolution is applied to the candidate set.
  If a single best indexer cannot be identified, the extension indexer access is ambiguous,
  and a compile-time error occurs.

Using this single best indexer identified at the previous step, the indexer access 
is then processed as a static method invocation.  

Depending on the context in which it is used, an indexer access causes invocation of either 
the *get_accessor* or the *set_accessor* of the indexer.  
If the indexer access is the target of an assignment, the *set_accessor* static implementation method
is invoked to assign a new value.  
In all other cases, the *get_accessor* static implementation method is invoked
to obtain the current value.  
Either way, the invocation will use generic arguments inferred during the applicability check and
the receiver as the first argument.

#### Extension implicit indexer access

An extension implicit `System.Index` (or `System.Range`) indexer access is applicable if:
1. the **element_access** has a single argument that is of type `System.Index` (or `System.Range`), and
2. an applicable `Length` or `Count` (instance or extension) property is found on the receiver type, and
3. an applicable `this[int]` (or `Slice(int, int)`) (instance or extension) indexer is found on the receiver type.

### Other element-access forms

Any construct that defers to element-access binding (null-conditional element access or assignments,
index assignments in object initializers, or list and spread patterns) automatically
participates in the extension indexer resolution described above.

### Expression trees

Extension indexers cannot be captured in expression trees.

### XML docs

CREF syntax allows referring to an extension indexer and its accessors, as well as its implementation methods.

Example:
```csharp
/// <see cref="E.extension(int).this[string]"/>
/// <see cref="E.extension(int).get_Item(string)"/>
/// <see cref="E.extension(int).get_Item"/>
/// <see cref="E.extension(int).set_Item(string, int)"/>
/// <see cref="E.extension(int).set_Item"/>
/// <see cref="E.get_Item(int, string)"/>
/// <see cref="E.get_Item"/>
/// <see cref="E.set_Item(int, string, int)"/>
/// <see cref="E.set_Item"/>
public static class E
{
    extension(int i)
    {
        /// <summary></summary>
        public int this[string s]
        {
            get => throw null;
            set => throw null;
        }
    }
}
```

## Metadata

Extension indexers follow the same lowering model as extension properties. For
each CLR-level extension grouping type that contains at least one indexer, the
compiler emits:

- An extension property named `Item` (or the value supplied by
    `IndexerNameAttribute`) with accessor bodies that `throw NotImplementedException()`
    and an `[ExtensionMarkerName]` attribute referencing the appropriate extension
    marker type.
- Implementation methods named `get_Item`/`set_Item` in the enclosing static
    class. These methods prepend the receiver parameter to the parameter list and
    contain the user-defined bodies. They are `static` and participate in overload
    resolution in the same way as implementation methods for extension properties.

To mirror the behavior of ordinary indexers, the compiler also emits
`[DefaultMemberAttribute]` on any extension grouping type that contains one or
more extension indexers. The attribute’s `MemberName` equals the metadata name of
the indexer (`Item` by default, or the value from `IndexerNameAttribute`).

### Example

Source code:

```csharp
static class BitExtensions
{
    extension<T>(T t)
    {
        public bool this[int index]
        {
            get => ...;
            set => ...;
        }
    }
}
```

Emitted metadata (simplified to C#-like syntax):

```csharp
[Extension]
static class BitExtensions
{
    [Extension, SpecialName, DefaultMember("Item")]
    public sealed class <G>$T0 // grouping type
    {
        [SpecialName]
        public static class <M>$T_t // marker type
        {
            [SpecialName]
            public static void <Extension>$(T t) { } // marker method
        }

        [ExtensionMarkerName("<M>$T_t")]
        public bool this[int index] // extension indexer
        {
            get => throw new NotImplementedException();
            set => throw new NotImplementedException();
        }
    }

    // accessor implementation methods
    public static bool get_Item<T>(T t, int index) => ...;
    public static void set_Item<T>(T t, int index, bool value) => ...;
}
```

## Open issues

<details>
<summary>Temporary section of the document related to open issues, including discussion of alternative designs</summary>

### ~~Dealing with `params`~~

If you have an extension indexer with `params`, such as `int this[int i, params string[] s] { get; set; }`,
there are three ways you could use it:
- extension indexing:  `receiver[i: 0, "Alice", "Bob"]`
- getter implementation invocation: `E.get_Item(receiver, i: 0, "Alice", "Bob")`
- setter implementation invocation: `E.set_Item(...)`

But what is the signature of the setter implementation method?  
It only makes sense for the last parameter of a user-invocable method signature to have `params`,
so it serves no purpose in `E.set_Item(... extension parameter ..., this i, params string[] s, int value)`.

Some options:
1. disallow `params` for extension indexers that have a setter
2. omit the `[ParamArray]` attribute on the setter implementation method
3. do nothing special (emit the `[ParamArray]`)

I would propose option 2, as it maximizes `params` usefulness. The cost is only a small difference between
extension indexing and disambiguation syntax.

Decision (LDM 2026-02-02): emit the `[ParamArray]` and verify no negative impact on tooling

### ~~Impact of assigned value to type inference~~

```csharp
int i = 0;
i[42, null] = new object(); // fails inference
E.set_Item(i, 42, null, new object()); // infer `E.set_Item<object>`

public static class E
{
    extension<T>(int i)
    {
        public T this[int j, T t] { set { } }
    }
}
```

```csharp
#nullable enable

int i = 0;
i[new object()] = null; // infer `E.extension<object!>` and warn on conversion of null literal to `object!`
E.set_Item(i, new object(), null); // infer `E.set_Item<object?>`

public static class E
{
    extension<T>(int i)
    {
        public T this[T t] { set { } }
    }
}
```

Decision (LDM 2026-02-02): the indexer is inferred only given the receiver and arguments in the argument list (ie. the assigned value doesn't contribute).

### ~~Should extension `Length`/`Count` properties make a type countable?~~

As a reminder, extensions do not come into play when binding [implicit Index or Range indexers](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#adding-index-and-range-support-to-existing-library-types):
```csharp
C c = new C();
_ = c[..]; // Cannot apply indexing with [] to an expression of type 'C'

class C
{
    public int Length => 0;
}

static class E
{
    public static C Slice(this C c, int i, int j) => null!;
}
```

So our position so far has been that extensions properties should not count as "countable properties"
in list-patterns, collection expressions and implicit indexers.

If we expose `this[Index]` or `this[Range]` extension indexers in element access scenarios,
it is natural to expect the target type to work in list patterns.  
List patterns, however, require a `Length` or `Count` property.

Should extension properties satisfy that requirement? (that would seem natural)

```csharp
C c = new C();
var x1 = c[^1];
var x2 = c[1..];

if (c is [.., var y1]) { }
if (c is [_, .. var y2]) { }

class C { }

static class E
{
  extension(C c)
  {
    object this[System.Index] => ...;
    C this[System.Range] => ...;
    int Length => ...;
  }
}
```

But then, should those properties also contribute to the implicit indexer fallback
(`Length`/`Count` + `Slice`) that is used when an explicit `Index`/`Range` indexer
is missing?

```csharp
C c = new C();
if (c is [var y1, .. var y2]) { }

class C
{
  C Slice(int i, int j) => ...;
}

static class E
{
  extension(C c)
  {
    object this[System.Index] => ...;
    int Length => ...;
  }
}
```

Decision (LDM 2026-02-02): extensions should contribute everywhere, including countable properties and implicit indexer fallback.

### ~~Confirm whether extension indexer access comes before or after implicit indexers~~

```csharp
C c = new C();
_ = c[^1];

class C
{
  public int Length => ...;
  public int this[int i] => ...;
}

static class E
{
  extension(C c)
  {
    public int this[System.Index i] => ...;
  }
}
```

I've spec'ed and implemented extension indexer access as having priority over implicit indexers,
but now think they should come after to avoid unnecessary compat breaks.

Update (LDM 2026-02-02): this needs further investigation. Yes, extensions should come after non-extension members,
but beyond that we need some concrete proposals in light of above decision to allow extensions to contribute to implicit indexer fallback.

Decision (LDM 2026-03-09): the order is real instance indexers, then implicit instance indexers, then real extension indexers, then implicit extension indexers.
See https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers

### ~~Count/Length: Is the name prioritized first, or non-extension vs extension?~~

We also have an existing fallback: `Length` is prioritized over `Count` property.
Should an extension `Length` come before or after a non-extension `Count` property?

Decision (LDM 2026-03-09): we'll look scope-by-scope (starting from instance scope and proceeding through extension scopes), and within each scope we'll look for `Length` first and then `Count`.
See https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers

### ~~Confirm proposed design for implicit indexers~~

We look scope-by-scope, starting from instance scope and then proceeding to extension scopes.  
Within a scope:
1. we look for a real indexer,
2. otherwise, if we have a single argument of the right type, then we look for an implicit indexer.

Decision (LDM 2026-03-09): no, once we move into extension scopes, we're going to use all-the-way-through extension resolution.

### ~~Confirm proposed design for list-patterns~~

For a list-pattern with a spread, we will look for:
1. a `Length`/`Count`
2. a real or implicit `this[Index]`
3. a real or implicit `this[Range]`

We will look for each independently.
But when we look for an implicit `this[Index]` or `this[Range]`, the two parts must come from the same scope:
1. `Length`/`Count`
2. `this[int]`/`Slice(int, int)`

Note: the non-negative handling for `Length` patterns kicks in when a type can be used in a list-pattern (ie. it is countable and indexable).

Decision (LDM 2026-03-09): no, we'll follow this lookup order instead:
1. List patterns are resolved as if we look for Length/Count, Index indexer and Range indexer individually
2. For Index and Range indexers, proceed as follows:
    a. With instance lookup only, find the "real" index if possible
    b. With instance lookup only, find the parts of the implicit indexer if possible
    c. With full lookup (instance+extension), find the "real" index if possible
    d. With full lookup (instance+extension), find the parts of the implicit indexer if possible (each in individual lookups)

### ~~Should extension `Slice` method also contribute?~~

```cs
_ = c[1..^1];

static class E
{
  extension(C c)
  {
    public int Length => 3;
  }
  public static C Slice(this C c, int i, int j) => ...;
}
```

Decision (LDM 2026-03-09): yes, we're treating classic and new extension methods exactly the same.

### ~~Should extension `Length` contribute to spread optimization?~~

```cs
C c = new C();
int[] i = [0, .. c]; // Uses Length, if available, to allocate the right size
```

Decision (LDM 2026-03-09): no, it's unlikely that an extension would be able to implement this in a performant way, so it would not help for optimization.

### ~~Should implicit indexers come into play for array or string types?~~

I propose that implicit indexers not contribute to those scenarios, because they are only possible with a malformed corlib (missing instance `Length`, `GetSubArray` or `GetSubstring`).

Decision (LDM/Mads by email 2026-04-07): no extension indexers on strings or arrays.

### ~~Should extension indexers come into play for array or string types?~~

The current spec and baseline rules for [array access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128122-array-access) and [string access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128123-string-access) mean that extension indexers don't work on arrays or strings.  
Yet the declaration of such extension indexers is permitted.  

```cs
int[] i = ...;
_ = i[new C()];

public static class E
{
    extension(int[] i)
    {
        public int this[C c] => throw null;
    }
}
```

```cs
string s = "...;
_ = s[new C()];

public static class E
{
    extension(int[] i)
    {
        public int this[C c] => throw null;
    }
}
```
Note: there is no question for [pointer element access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2464-pointer-element-access) since an extension parameter may not be a pointer type. 

Decision (LDM/Mads by email 2026-04-07): no extension indexers on strings or arrays.

</details>
