Indexeerders voor uitbreidingen

Note

Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.

Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).

Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.

Kampioensprobleem: https://github.com/dotnet/csharplang/issues/9856

Verklaring

Grammatica

Uitbreidingsindexeerfuncties worden toegevoegd aan de set toegestane leden in een uitbreidingsdeclaratie door de grammatica als volgt uit te breiden (ten opzichte van voorstellen/csharp-14.0/extensions.md):

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

Net als gewone indexeerfuncties hebben extensie-indexeerfuncties geen id en worden ze geïdentificeerd door hun parameterlijst. Extensie-indexeerfuncties kunnen de volledige set functies gebruiken die gewone indexeerfuncties vandaag ondersteunen (toegangsorganen, expressie-bodyied leden, ref-retournerende accessors, scoped parameters, kenmerken, enzovoort).

Omdat indexeerfuncties altijd exemplaarleden zijn, moet een extensieblok dat een indexeerfunctie declareert een benoemde ontvangerparameter opgeven.

De bestaande beperkingen voor extensieleden blijven van toepassing: indexeerfuncties in een uitbreidingsdeclaratie kunnen niet opgevenabstract, , virtualoverride, new, , sealed, partial, protected (of een van de gerelateerde toegankelijkheidsaanpassingen) of init accessors.

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

Alle regels van de C#-standaard die van toepassing zijn op gewone indexeerfuncties, zijn van toepassing op extensie-indexeerfuncties, maar extensieleden hebben geen impliciete of expliciete this.

De bestaande extensielidregel is nog steeds van toepassing: voor elk lid van de extensie die niet van de methode is, moeten alle typeparameters van het extensieblok worden gebruikt in de gecombineerde set parameters van de extensie en het lid.

IndexerName-kenmerk

IndexerNameAttribute kan worden toegepast op een extensie-indexeerfunctie. Het kenmerk wordt niet verzonden in metagegevens, maar de waarde ervan is van invloed op conflicten tussen leden, het bepaalt de naam van de eigenschap en accessors in metagegevens en wordt gebruikt bij het verzenden [DefaultMemberAttribute] (zie Metagegevens).

Consumptie

Toegang tot indexeerfunctie

De regels in de toegang tot de indexeerfunctie worden bijgewerkt: als de normale verwerking van de indexeerfunctie geen toepasselijke indexeerfunctie vindt, wordt geprobeerd de constructie te verwerken als een uitbreidingsindexeerfunctietoegang.

  1. Probeer verbinding te maken met behulp van de exemplaarindexeerfuncties die zijn gedeclareerd (of overgenomen) voor het ontvangertype. Als er een toepasselijke kandidaat wordt gevonden, selecteert overbelastingsoplossing de leden van deze exemplaren zoals vandaag en stopt deze.
  2. Probeer te binden met behulp van de impliciete exemplaarindexeerfuncties die zijn gedeclareerd (of overgenomen) voor het ontvangertype. Als er een toepasselijke kandidaat wordt gevonden, selecteert overbelastingsoplossing de leden van deze exemplaren zoals vandaag en stopt deze.
  3. Probeer verbinding te maken als toegang tot een extensie-indexeerfunctie. Als met dit proces een toepasselijke kandidaat wordt gevonden, selecteert overbelastingsresolutie onder die uitbreidingsleden, zoals hieronder wordt beschreven en stopt.
  4. Probeer verbinding te maken als impliciete toegang tot een extensieindexeerfunctie. Als met dit proces een toepasselijke kandidaat wordt gevonden, selecteert overbelastingsresolutie onder die uitbreidingsleden, zoals hieronder wordt beschreven en stopt.

Opmerking: de sectie elementtoegang verwerkt het geval waarin een argument type dynamicheeft, zodat het nooit wordt verwerkt als een indexeerfunctietoegang.

Toegang tot extensie-indexeerfunctie

Extensieleden, inclusief extensie-indexeerfuncties, worden nooit overwogen wanneer de ontvanger een base_access expressie is.

Opmerking: we verwerken alleen een element_access als een indexeerfunctietoegang als de ontvanger een variabele of waarde is, dus extensie-indexeerfuncties worden nooit overwogen wanneer de ontvanger een type is.

Gezien een element_accessE[A] is het doel om een extensie-indexeerfunctie te identificeren.

Een kandidaat-extensie-indexeerfunctie is van toepassing met betrekking tot ontvanger E en argumentenlijst A als een uitgebreide handtekening, bestaande uit de typeparameters van het extensieblok en een parameterlijst die de extensieparameter combineert met de parameters van de indexeerfunctie, van toepassing is met betrekking tot een lijst met argumenten die de ontvanger E combineert met de lijst Amet argumenten.

We gebruiken de bereik-walk van de extensiemethode opnieuw: we doorlopen dezelfde bereiken die worden geraadpleegd voor aanroepen van extensiemethoden, inclusief de huidige en omsluit lexicale bereiken en using naamruimte of using static import.

Houd rekening met elk bereik op zijn beurt:

  • Extensieblokken in niet-algemene statische klassedeclaraties in het huidige bereik worden overwogen.
  • De indexeerfuncties in deze extensieblokken bestaan uit de kandidaatset.
  • Kandidaten die niet toegankelijk zijn, worden uit de set verwijderd.
  • Kandidaten die niet van toepassing zijn (zoals hierboven gedefinieerd) worden uit de set verwijderd.
  • Als de resulterende set kandidaat-indexeerfuncties leeg is, gaan we verder met het volgende bereik of lossen we de toegang tot een extensie-indexeerfunctie niet op als we het laatste bereik hebben bereikt (we blijven proberen om op te lossen als impliciete extensie-indexeerfunctie in dat geval).
  • Anders wordt overbelastingsresolutie toegepast op de kandidaatset. Als één beste indexeerfunctie niet kan worden geïdentificeerd, is de toegang tot de extensie-indexeerfunctie niet eenduidig en treedt er een compilatiefout op.

Met behulp van deze beste indexeerfunctie die in de vorige stap is geïdentificeerd, wordt de toegang tot de indexeerfunctie vervolgens verwerkt als een statische methode-aanroep.

Afhankelijk van de context waarin deze wordt gebruikt, veroorzaakt een indexeerfunctietoegang de aanroep van de get_accessor of de set_accessor van de indexeerfunctie.
Als de toegang tot de indexeerfunctie het doel is van een toewijzing, wordt de set_accessor statische implementatiemethode aangeroepen om een nieuwe waarde toe te wijzen.
In alle andere gevallen wordt de get_accessor statische implementatiemethode aangeroepen om de huidige waarde te verkrijgen.
In beide gevallen gebruikt de aanroep algemene argumenten die zijn afgeleid tijdens de toepasselijkheidscontrole en de ontvanger als eerste argument.

Impliciete toegang tot indexeerfunctie voor extensies

Een impliciete System.Index (of System.Range) uitbreidingsindexeertoegang is van toepassing als:

  1. de element_access één argument heeft dat van het type System.Index (ofSystem.Range) is en
  2. een toepasselijke Length of Count (instantie- of extensie)-eigenschap wordt gevonden op het type ontvanger en
  3. een toepasselijke this[int] (of Slice(int, int)) indexeerfunctie (instantie of extensie) wordt gevonden op het type ontvanger.

Andere formulieren voor elementtoegang

Elke constructie die wordt uitgesteld tot binding voor elementtoegang (null-voorwaardelijke elementtoegang of -toewijzingen, indextoewijzingen in object-initializers of lijst- en verspreidingspatronen) neemt automatisch deel aan de resolutie van de extensie-indexeerfunctie die hierboven wordt beschreven.

Expressiebomen

Extensie-indexeerfuncties kunnen niet worden vastgelegd in expressiestructuren.

XML-documenten

CREF-syntaxis maakt het mogelijk om te verwijzen naar een extensie-indexeerfunctie en de bijbehorende accessors, evenals de implementatiemethoden.

Voorbeeld:

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

Extensieindexeerfuncties volgen hetzelfde lagere model als extensie-eigenschappen. Voor elk type uitbreidingsgroepering op CLR-niveau dat ten minste één indexeerfunctie bevat, verzendt de compiler:

  • Een extensie-eigenschap met de naam Item (of de waarde die wordt opgegeven door IndexerNameAttribute) met toegangsteksten die throw NotImplementedException() en een [ExtensionMarkerName] kenmerk verwijzen naar het juiste type extensiemarkering.
  • Implementatiemethoden die zijn genoemd get_Item/set_Item in de statische klasse. Met deze methoden wordt de ontvangerparameter voorafgegaan aan de lijst met parameters en worden de door de gebruiker gedefinieerde lichamen weergegeven. Ze zijn static en nemen op dezelfde manier deel aan overbelastingsresolutie als implementatiemethoden voor extensie-eigenschappen.

Om het gedrag van gewone indexeerfuncties te spiegelen, wordt de compiler ook verzonden [DefaultMemberAttribute] op elk type extensiegroepering dat een of meer extensie-indexeerfuncties bevat. De kenmerk MemberName is gelijk aan de metagegevensnaam van de indexeerfunctie (Item standaard of de waarde van IndexerNameAttribute).

Example

Broncode:

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

Verzonden metagegevens (vereenvoudigd naar C#-achtige syntaxis):

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

Openstaande problemen

Tijdelijke sectie van het document met betrekking tot openstaande problemen, waaronder bespreking van alternatieve ontwerpen

Omgaan met params

Als u een extensie-indexeerfunctie hebt met params, zoals int this[int i, params string[] s] { get; set; }, zijn er drie manieren waarop u deze kunt gebruiken:

  • extensie-indexering: receiver[i: 0, "Alice", "Bob"]
  • aanroep van getter-implementatie: E.get_Item(receiver, i: 0, "Alice", "Bob")
  • aanroep van setter-implementatie: E.set_Item(...)

Maar wat is de handtekening van de implementatiemethode setter?
Het is alleen zinvol voor de laatste parameter van een door de gebruiker aan te roepen methodehandtekening, paramsdus het heeft geen doel in E.set_Item(... extension parameter ..., this i, params string[] s, int value).

Enkele opties:

  1. niet toe te laten params voor extensie-indexeerfuncties met een setter
  2. laat het [ParamArray] kenmerk op de implementatiemethode van de setter weg
  3. niets bijzonders doen (de [ParamArray])

Ik zou optie 2 voorstellen, omdat het de bruikbaarheid maximaliseert params . De kosten zijn slechts een klein verschil tussen extensie-indexering en ondubbelzinnige syntaxis.

Beslissing (LDM 2026-02-02): het verzenden en verifiëren van geen [ParamArray] negatieve gevolgen voor hulpprogramma's

Impact van toegewezen waarde om deductie te typen

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

Beslissing (LDM 2026-02-02): de indexeerfunctie wordt alleen afgeleid van de ontvanger en argumenten in de lijst met argumenten (de toegewezen waarde draagt niet bij).

Moeten extensie-eigenschappen Length/Count een type tellen?

Ter herinnering: extensies worden niet afgespeeld wanneer impliciete index- of bereikindexeerfuncties voor bindingen worden gebruikt:

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

Onze positie tot nu toe is dus dat uitbreidingseigenschappen niet moeten worden meegeteld als 'aantal eigenschappen' in lijstpatronen, verzamelingsexpressies en impliciete indexeerfuncties.

Als we indexeerfuncties beschikbaar maken this[Index] of this[Range] uitbreiden in toegangsscenario's voor elementen, is het natuurlijk logisch dat het doeltype werkt in lijstpatronen.
Lijstpatronen vereisen echter een Length of Count eigenschap.

Moeten extensie-eigenschappen aan die vereiste voldoen? (dat lijkt natuurlijk)

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

Maar moeten die eigenschappen ook bijdragen aan de impliciete terugval van de indexeerfunctie (LengthSlice + /Count) die wordt gebruikt wanneer een expliciete Index/Range indexeerfunctie ontbreekt?

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

Beslissing (LDM 2026-02-02): extensies moeten overal bijdragen, inclusief talbare eigenschappen en impliciete terugval van indexeerfunctie.

Controleer of de toegang tot extensie-indexeerfuncties vóór of na impliciete indexeerfuncties wordt geleverd

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

Ik heb de toegang tot extensieindexeerfuncties opgegeven en geïmplementeerd als prioriteit boven impliciete indexeerfuncties, maar denk nu dat ze moeten komen om onnodige compatibiliteitseinden te voorkomen.

Update (LDM 2026-02-02): dit moet verder worden onderzocht. Ja, uitbreidingen moeten na niet-extensieleden komen, maar daarbuiten hebben we een aantal concrete voorstellen nodig in het licht van bovenstaande beslissing om uitbreidingen toe te staan bij impliciete terugval van indexeerfunctie.

Beslissing (LDM 2026-03-09): de volgorde is echte exemplaarindexeerfuncties, vervolgens impliciete indexeerfuncties voor exemplaren, vervolgens echte extensie-indexeerfuncties en vervolgens impliciete extensie-indexeerfuncties. Zie https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers

Aantal/lengte: Heeft de naam prioriteit als eerste of niet-extensie versus extensie?

We hebben ook een bestaande terugval: Length heeft prioriteit boven Count eigenschap. Moet een extensie Length vóór of na een niet-extensie-eigenschap Count komen?

Beslissing (LDM 2026-03-09): we kijken naar scope-by-scope (beginnend bij exemplaarbereik en door te gaan via uitbreidingsbereiken), en binnen elk bereik zoeken Length we eerst en vervolgens Count. Zie https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers

Voorgesteld ontwerp bevestigen voor impliciete indexeerfuncties

We kijken naar bereik per bereik, beginnend bij exemplaarbereik en vervolgens verdergaan met uitbreidingsbereiken.
Binnen een bereik:

  1. we zoeken naar een echte indexeerfunctie,
  2. als we een enkel argument van het juiste type hebben, zoeken we naar een impliciete indexeerfunctie.

Beslissing (LDM 2026-03-09): nee, zodra we overschakelen naar uitbreidingsbereiken, gaan we de uitbreidingsresolutie in alle richtingen gebruiken.

Voorgesteld ontwerp voor lijstpatronen bevestigen

Voor een lijstpatroon met een spread zoeken we naar:

  1. a Length/Count
  2. een echt of impliciet this[Index]
  3. een echt of impliciet this[Range]

We zoeken elk afzonderlijk. Maar wanneer we zoeken naar een impliciete this[Index] of this[Range], moeten de twee onderdelen afkomstig zijn van hetzelfde bereik:

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

Opmerking: de niet-negatieve verwerking voor Length patronen wordt geactiveerd wanneer een type kan worden gebruikt in een lijstpatroon (bijvoorbeeld het kan worden geteld en indexeerbaar).

Beslissing (LDM 2026-03-09): nee, in plaats daarvan volgen we deze opzoekvolgorde:

  1. Lijstpatronen worden opgelost alsof we zoeken naar lengte/aantal, indexindexeerfunctie en bereikindexeerfunctie afzonderlijk
  2. Voor index- en bereikindexeerfuncties gaat u als volgt te werk: a. Zoek met alleen exemplaarzoekactie de 'echte' index indien mogelijk b. Zoek met alleen exemplaarzoekacties de onderdelen van de impliciete indexeerfunctie indien mogelijk c. Met volledige zoekactie (instance+extension) zoekt u indien mogelijk de echte index d. Met volledige zoekactie (instance+extension) vindt u indien mogelijk de onderdelen van de impliciete indexeerfunctie (elk in afzonderlijke zoekacties)

Moet de extensiemethode Slice ook bijdragen?

_ = c[1..^1];

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

Beslissing (LDM 2026-03-09): ja, we behandelen klassieke en nieuwe uitbreidingsmethoden precies hetzelfde.

Moet extensie Length bijdragen aan de optimalisatie van de verspreiding?

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

Beslissing (LDM 2026-03-09): nee, het is onwaarschijnlijk dat een extensie dit op een performante manier zou kunnen implementeren, dus het zou niet helpen voor optimalisatie.

Moeten impliciete indexeerfuncties worden afgespeeld voor matrix- of tekenreekstypen?

Ik stel voor dat impliciete indexeerfuncties niet bijdragen aan deze scenario's, omdat ze alleen mogelijk zijn met een onjuist gevormde corlib (ontbrekend exemplaar Lengthof GetSubArrayGetSubstring).

Beslissing (LDM/Mads per e-mail 2026-04-07): geen extensie-indexeerfuncties voor tekenreeksen of matrices.

Moeten extensie-indexeerfuncties worden afgespeeld voor matrix- of tekenreekstypen?

De huidige specificatie- en basislijnregels voor matrixtoegang en toegang tot tekenreeksen betekenen dat extensie-indexeerfuncties niet werken voor matrices of tekenreeksen.
Toch is de declaratie van dergelijke extensie-indexeerfuncties toegestaan.

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

Opmerking: er is geen vraag voor toegang tot aanwijzerelementen , omdat een extensieparameter mogelijk geen aanwijzertype is.

Beslissing (LDM/Mads per e-mail 2026-04-07): geen extensie-indexeerfuncties voor tekenreeksen of matrices.