Tilläggsindexerare

Note

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningarna (Language Design Meeting).

Du kan lära dig mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-fråga: https://github.com/dotnet/csharplang/issues/9856

Deklaration

Grammatik

Tilläggsindexerare läggs till i uppsättningen tillåtna medlemmar i en tilläggsdeklaration genom att grammatiken utökas enligt följande (i förhållande till förslag/csharp-14.0/extensions.md):

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

Precis som vanliga indexerare har tilläggsindexerare ingen identifierare och identifieras av deras parameterlista. Tilläggsindexerare kan använda den fullständiga uppsättning funktioner som vanliga indexerare stöder idag (accessor-organ, uttrycksbaserade medlemmar, ref-returnerande accessorer, scoped parametrar, attribut osv.).

Eftersom indexerare alltid är instansmedlemmar måste ett tilläggsblock som deklarerar en indexerare ange en namngiven mottagarparameter.

De befintliga begränsningarna för tilläggsmedlemmar fortsätter att gälla: indexerare i en tilläggsdeklaration kan inte ange abstract, , overridevirtual, new, sealed, partialprotected (eller någon av de relaterade hjälpmedelsmodifierarna) eller init accessorer.

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

Alla regler från C#-standarden som gäller för vanliga indexerare gäller för tilläggsindexerare, men tilläggsmedlemmar har ingen implicit eller explicit this.

Den befintliga inferensregeln för tilläggsmedlem gäller fortfarande: För varje medlem i tilläggstillägget som inte är en metod måste alla typparametrar i dess tilläggsblock användas i den kombinerade uppsättningen parametrar från tillägget och medlemmen.

IndexerName-attribut

IndexerNameAttribute kan tillämpas på en tilläggsindexerare. Attributet genereras inte i metadata, men dess värde påverkar konflikter mellan medlemmar, det bestämmer namnet på egenskapen och accessorerna i metadata och används när det [DefaultMemberAttribute] genererar (se Metadata).

Consumption

Indexerarens åtkomst

Reglerna i Indexer-åtkomst uppdateras: om den normala bearbetningen av indexerarens åtkomst inte hittar någon tillämplig indexerare görs ett försök att bearbeta konstruktionen som en tilläggsindexerareåtkomst.

  1. Försök att binda med de instansindexerare som deklarerats (eller ärvt) på mottagartypen. Om en lämplig kandidat hittas väljer överbelastningsmatchning bland instansmedlemmarna som i dag och stoppas.
  2. Försök att binda med implicita instansindexerare deklarerade (eller ärvda) på mottagartypen. Om en lämplig kandidat hittas väljer överbelastningsmatchning bland instansmedlemmarna som i dag och stoppas.
  3. Försök att binda som en tilläggsindexerareåtkomst. Om den här processen hittar en lämplig kandidat väljer överlagringslösning bland dessa tilläggsmedlemmar enligt beskrivningen nedan och stoppar.
  4. Försök att binda som implicit indexerareåtkomst för tillägg. Om den här processen hittar en lämplig kandidat väljer överlagringslösning bland dessa tilläggsmedlemmar enligt beskrivningen nedan och stoppar.

Obs! Avsnittet för elementåtkomst hanterar fallet där ett argument har typen dynamic, så att det aldrig bearbetas som en indexerares åtkomst.

Tilläggsindexerarens åtkomst

Tilläggsmedlemmar, inklusive tilläggsindexerare, beaktas aldrig när mottagaren är ett base_access uttryck.

Obs! Vi bearbetar bara en element_access som en indexerares åtkomst om mottagaren är en variabel eller ett värde, så tilläggsindexerare beaktas aldrig när mottagaren är en typ.

Med tanke på en element_accessE[A] är målet att identifiera en tilläggsindexerare.

En kandidattilläggsindexerare gäller för mottagar E - och argumentlistan A om en utökad signatur, som består av typparametrarna i tilläggsblocket och en parameterlista som kombinerar tilläggsparametern med indexerarens parametrar, gäller för en argumentlista som kombinerar mottagaren E med argumentlistan A.

Vi återanvänder scope-walk för tilläggsmetoden: vi går igenom samma omfång som konsulteras för anrop av tilläggsmetod, inklusive aktuella och omslutande lexikala omfång och using namnrymd eller using static importer.

Med tanke på varje omfång i tur och ordning:

  • Tilläggsblock i icke-generiska statiska klassdeklarationer i det aktuella omfånget beaktas.
  • Indexerarna i dessa tilläggsblock utgör kandidatuppsättningen.
  • Kandidater som inte är tillgängliga tas bort från uppsättningen.
  • Kandidater som inte är tillämpliga (enligt definitionen ovan) tas bort från uppsättningen.
  • Om den resulterande uppsättningen kandidatindexerare är tom fortsätter vi till nästa omfång eller misslyckas med att lösa en tilläggsindexerares åtkomst om vi nådde det sista omfånget (vi fortsätter att försöka lösa det som en implicit indexerare för tillägg i så fall).
  • Annars tillämpas överbelastningsmatchning på kandidatuppsättningen. Om det inte går att identifiera en enda bästa indexerare är tilläggsindexerarens åtkomst tvetydig och ett kompileringsfel inträffar.

Med hjälp av den här bästa indexeraren som identifierades i föregående steg bearbetas indexerarens åtkomst sedan som en statisk metodanrop.

Beroende på i vilken kontext den används orsakar en indexerare åtkomst anrop av antingen get_accessor eller indexerarens set_accessor .
Om indexerarens åtkomst är målet för en tilldelning anropas den set_accessor statiska implementeringsmetoden för att tilldela ett nytt värde.
I alla andra fall anropas den get_accessor statiska implementeringsmetoden för att hämta det aktuella värdet.
Hur som helst använder anropet allmänna argument som härleds under tillämplighetskontrollen och mottagaren som det första argumentet.

Implicit indexerareåtkomst för tillägg

En implicit System.Index (eller System.Range) tilläggsindexeringsåtkomst gäller om:

  1. element_access har ett enda argument som är av typen System.Index (eller System.Range), och
  2. en tillämplig Length egenskap eller Count (instans eller tillägg) finns på mottagartypen, och
  3. en tillämplig this[int] indexerare (eller Slice(int, int)) (instans eller tillägg) finns på mottagartypen.

Andra formulär för elementåtkomst

Alla konstruktioner som defersar till elementåtkomstbindning (null-villkorlig elementåtkomst eller tilldelningar, indextilldelningar i objektinitierare eller list- och spridningsmönster) deltar automatiskt i tilläggsindexerarens lösning som beskrivs ovan.

Uttrycksträd

Tilläggsindexerare kan inte avbildas i uttrycksträd.

XML-dokument

CREF-syntax gör det möjligt att referera till en tilläggsindexerare och dess åtkomstorer, samt dess implementeringsmetoder.

Exempel:

/// <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

Tilläggsindexerare följer samma sänkningsmodell som tilläggsegenskaper. För varje clr-nivåtilläggsgrupperingstyp som innehåller minst en indexerare genererar kompilatorn:

  • En tilläggsegenskap med namnet Item (eller värdet som tillhandahålls av IndexerNameAttribute) med accessor-organ som throw NotImplementedException() och ett [ExtensionMarkerName] attribut som refererar till lämplig tilläggsmarkörtyp.
  • Implementeringsmetoder med namnet get_Item/set_Item i den omslutande statiska klassen. Dessa metoder förbereder mottagarparametern till parameterlistan och innehåller de användardefinierade organen. De är static och deltar i överbelastningsmatchning på samma sätt som implementeringsmetoder för tilläggsegenskaper.

För att spegla vanliga indexerares [DefaultMemberAttribute] beteende genererar kompilatorn även alla tilläggsgrupperingstyper som innehåller en eller flera tilläggsindexerare. Attributet är MemberName lika med metadatanamnet för indexeraren (Item som standard eller värdet från IndexerNameAttribute).

Example

Källkod:

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

Genererad metadata (förenklad till C#-liknande syntax):

[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) => ...;
}

Öppna ärenden

Tillfälligt avsnitt i dokumentet som rör öppna frågor, inklusive diskussion om alternativa design

Hantera params

Om du har en tilläggsindexerare med params, till exempel int this[int i, params string[] s] { get; set; }, finns det tre sätt att använda det:

  • tilläggsindexering: receiver[i: 0, "Alice", "Bob"]
  • Anrop för geter-implementering: E.get_Item(receiver, i: 0, "Alice", "Bob")
  • implementeringsanrop för setter: E.set_Item(...)

Men vad är signaturen för metoden för implementering av setter?
Det är bara meningsfullt att den sista parametern för en användarinvocable-metodsignatur har params, så att den inte har något syfte i E.set_Item(... extension parameter ..., this i, params string[] s, int value).

Några alternativ:

  1. params tillåt inte för tilläggsindexerare som har en setter
  2. [ParamArray] utelämna attributet på implementeringsmetoden för setter
  3. gör inget speciellt (avger [ParamArray])

Jag föreslår alternativ 2, eftersom det maximerar params användbarheten. Kostnaden är bara en liten skillnad mellan tilläggsindexering och disambiguationssyntax.

Beslut (LDM 2026-02-02): avge [ParamArray] och verifiera ingen negativ inverkan på verktyg

Effekten av tilldelat värde för typinferens

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 { } }
    }
}
#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 { } }
    }
}

Beslut (LDM 2026-02-02): Indexeraren härleds endast med tanke på mottagaren och argumenten i argumentlistan (dvs. det tilldelade värdet bidrar inte).

Ska tilläggsegenskaper Length/Count göra en typ räknare?

Som en påminnelse spelar tillägg inte in när implicita index- eller intervallindexerare binds:

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!;
}

Så vår ståndpunkt hittills har varit att tilläggsegenskaper inte ska räknas som "countable properties" i listmönster, samlingsuttryck och implicita indexerare.

Om vi exponerar this[Index] eller this[Range] tilläggsindexerare i scenarier för elementåtkomst är det naturligt att förvänta sig att måltypen fungerar i listmönster.
Listmönster kräver dock en Length eller Count -egenskap.

Ska tilläggsegenskaper uppfylla det kravet? (det verkar naturligt)

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 => ...;
  }
}

Men bör dessa egenskaper också bidra till den implicita indexerarens reserv (Length + Slice/Count) som används när en explicit Index/Range indexerare saknas?

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 => ...;
  }
}

Beslut (LDM 2026-02-02): tillägg bör bidra överallt, inklusive räknare och implicit indexerares återställning.

Bekräfta om tilläggsindexerarens åtkomst kommer före eller efter implicita indexerare

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] => ...;
  }
}

Jag har spec'ed och implementerat tillägg indexerare åtkomst som har prioritet framför implicita indexerare, men nu tror att de bör komma efter för att undvika onödiga kompatibilitetsbrytningar.

Uppdatering (LDM 2026-02-02): Detta behöver undersökas ytterligare. Ja, tillägg bör komma efter icke-förlängningsmedlemmar, men utöver det behöver vi några konkreta förslag mot bakgrund av ovanstående beslut för att tillåta förlängningar att bidra till implicit indexerare reserv.

Beslut (LDM 2026-03-09): ordningen är riktiga instansindexerare, sedan implicita instansindexerare, sedan verkliga tilläggsindexerare och sedan implicita tilläggsindexerare. Se https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers

Antal/längd: Prioriteras namnet först, eller inte tillägget jämfört med tillägget?

Vi har också en befintlig reserv: Length prioriteras framför Count egenskapen. Ska ett tillägg Length komma före eller efter en egenskap som inte är en tilläggsegenskap Count ?

Beslut (LDM 2026-03-09): Vi ska titta omfång för omfång (från instansomfång och fortsätta genom tilläggsomfång) och inom varje omfång letar Length vi först och sedan Count. Se https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers

Bekräfta den föreslagna designen för implicita indexerare

Vi tittar på omfång för omfång, från instansomfång och fortsätter sedan till tilläggsomfång.
Inom ett omfång:

  1. vi letar efter en riktig indexerare,
  2. Annars letar vi efter en implicit indexerare om vi har ett enda argument av rätt typ.

Beslut (LDM 2026-03-09): Nej, när vi har gått över till tilläggsomfång använder vi lösning för hela vägen genom tillägget.

Bekräfta den föreslagna designen för listmönster

För ett listmönster med en spridning letar vi efter:

  1. a Length/Count
  2. en verklig eller implicit this[Index]
  3. en verklig eller implicit this[Range]

Vi kommer att leta efter var och en separat. Men när vi letar efter en implicit this[Index] eller this[Range]måste de två delarna komma från samma omfång:

  1. Length/Count
  2. this[int]/Slice(int, int)

Obs! Den icke-negativa hanteringen för Length mönster börjar gälla när en typ kan användas i ett listmönster (dvs. det kan räknas och indexeras).

Beslut (LDM 2026-03-09): Nej, vi följer den här uppslagsordningen i stället:

  1. Listmönster matchas som om vi letar efter längd/antal, indexerare och intervallindexerare individuellt
  2. För index- och intervallindexerare fortsätter du enligt följande: a. Med endast instanssökning letar du reda på det "riktiga" indexet om möjligt b. Med endast instanssökning letar du reda på delarna i den implicita indexeraren om möjligt c. Med fullständig sökning (instans+tillägg) hittar du det "riktiga" indexet om möjligt d. Med fullständig sökning (instans+tillägg) hittar du delarna i den implicita indexeraren om möjligt (var och en i enskilda sökningar)

Bör tilläggsmetoden Slice också bidra?

_ = c[1..^1];

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

Beslut (LDM 2026-03-09): Ja, vi behandlar klassiska och nya tilläggsmetoder på exakt samma sätt.

Bör tillägget Length bidra till spridningsoptimering?

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

Beslut (LDM 2026-03-09): Nej, det är osannolikt att ett tillägg skulle kunna implementera detta på ett högpresterande sätt, så det skulle inte hjälpa för optimering.

Bör implicita indexerare spela in för matris- eller strängtyper?

Jag föreslår att implicita indexerare inte bidrar till dessa scenarier, eftersom de endast är möjliga med en felformad corlib (saknad instans Length, GetSubArray eller GetSubstring).

Beslut (LDM/Mads via e-post 2026-04-07): inga tilläggsindexerare på strängar eller matriser.

Bör tilläggsindexerare spela in för matris- eller strängtyper?

De aktuella specifikations- och baslinjereglerna för matrisåtkomst och strängåtkomst innebär att tilläggsindexerare inte fungerar på matriser eller strängar.
Ändå är deklarationen av sådana tilläggsindexerare tillåten.

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

public static class E
{
    extension(int[] i)
    {
        public int this[C c] => throw null;
    }
}
string s = "...;
_ = s[new C()];

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

Obs! Det är ingen fråga om åtkomst till pekarelement eftersom en tilläggsparameter kanske inte är en pekartyp.

Beslut (LDM/Mads via e-post 2026-04-07): inga tilläggsindexerare på strängar eller matriser.