meetings/2026/LDM-2026-02-02.md
Champion issue: https://github.com/dotnet/csharplang/issues/9856
Spec: https://github.com/dotnet/csharplang/blob/7a58785d96afc903219cb33f45360744211c3c4c/proposals/extension-indexers.md
The LDM reviewed the extension indexers proposal and had no comments on the overview.
Open question: https://github.com/dotnet/csharplang/blob/7a58785d96afc903219cb33f45360744211c3c4c/proposals/extension-indexers.md#dealing-with-params
When an extension indexer has a params parameter, the setter's implementation method ends up with params in a
non-final position (the value parameter comes after it). The question was whether to disallow this scenario or simply
emit the params attribute as-is.
This isn't actually an extension-specific problem: regular instance indexers with params already emit setters with the
params attribute in the middle. The difference is that for regular indexers, users can't directly invoke the accessor
methods, so they never observed this signature (though there is a possibility it can be observed through C# consuming a
VB non-default indexed property). With extension indexers, the implementation methods are invokable via the disambiguation
syntax, so it can be more directly observed.
Both the C# and VB compilers are designed to ignore params attributes on any parameter other than the last. While
we should verify that tooling (like IDE signature help) doesn't display params in confusing locations, the consensus
was that we should simply let things fall out naturally without special-casing this scenario.
Emit the params attribute on the setter implementation method even though it appears on a non-final parameter.
Compilers will ignore it in that position, maintaining consistency with how regular indexers already behave. We will
ensure that the IDE behaves well in these scenarios.
In C# 14, extension properties were explicitly excluded from contributing to implicit indexers (the pattern-based
indexing that uses Length/Count with an int indexer or Slice method). This was because extension methods also
didn't contribute to that pattern.
However, now that we're adding extension indexers that can directly take System.Index or System.Range, a question
arises: if you define an extension indexer taking Index and an extension Length property, should list patterns work
on that type?
class C { }
static class E
{
extension(C c)
{
object this[System.Index] => ...;
C this[System.Range] => ...;
int Length => ...;
}
}
// Should this work?
if (c is [.., var y]) { }
There was strong sentiment in the LDM for a "maximalist" approach: extension members should participate in language patterns wherever possible. This mostly matches our past approaches, and how we've loosened similar restrictions on other language features recently.
We considered whether to require that all members contributing to a pattern come from the "same origin" (same extension
block or all from the instance type). The rationale was to prevent confusing situations where unrelated extensions from
different libraries combine unexpectedly. However, counter-arguments noted that this would force users to redeclare
members that already exist (like Length), potentially causing ambiguity errors when both namespaces are imported. Those
extensions may not even be ever usable, if the issue was that the underlying type did not provide a Length, for example,
then an extension being forced to provide a Length when it can never be invoked as an extension would be odd.
The LDM concluded that enforcing same-origin rules would add more complexity than it prevents. If the type already has a
Length property that wasn't intended to participate in patterns, that's unfortunate, but preventing extensions from
filling in the rest of the pattern only works if the member happens to be named Length; otherwise an extension could
add it anyway. Analyzer rules could flag suspicious combinations if this becomes a real problem in practice.
A follow-up question was whether extension members should also contribute to implicit indexers (the fallback that
uses Length + int indexer instead of direct Index/Range indexers). The same maximalist reasoning applied: if
you add an extension int indexer, you shouldn't need to also add an Index indexer just to use c[^1].
We will allow permissive extension applicability. Extension members can contribute to pattern-based indexer matching from multiple sources.
The final open question concerned lookup ordering. When binding an element access, the current order is:
Index/Range signature)Length + int/Slice)The previous decision to allow extensions to contribute to implicit indexers opened a can of worms about priority. What
happens when an inner extension scope defines Length, an outer scope defines an int indexer, and an even outer scope
defines an Index indexer? Different orderings lead to different behavior.
Several approaches were discussed:
Index/Range indexers everywhere, then fall back to implicit patternsThere was agreement that instance members should be fully exhausted before looking at extensions, but the exact ordering
when multiple extension scopes are involved remained unclear. We think this requires more investigation with concrete
examples to understand all the consequences. Depending on how we do searches, and for what piece of the language, there
could be very odd priority inversions and member selections that are not obvious to readers. For example, if there are 3
scopes in order from Length, Index-based indexer, and int-based indexer, what is the expectation on what is being
called for underlying[^1]?
We need to revisit this question with a proposal that works through the combinations systematically. The extensions working group will investigate and bring back a more concrete recommendation.