Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Note
Questo articolo è una specifica delle funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono riportate nelle note pertinenti della riunione di progettazione linguistica (LDM) .
Ulteriori dettagli sul processo di adozione delle specifiche di funzionalità nello standard del linguaggio C# sono disponibili nell'articolo sulle specifiche .
Questione prioritaria: https://github.com/dotnet/csharplang/issues/9856
Dichiarazione
Grammatica
Gli indicizzatori di estensione vengono aggiunti al set di membri consentiti all'interno di una dichiarazione di estensione estendendo la grammatica come indicato di seguito (rispetto alle proposte/csharp-14.0/extensions.md):
extension_member_declaration
: method_declaration
| property_declaration
| indexer_declaration // new
| operator_declaration
;
Come gli indicizzatori ordinari, gli indicizzatori di estensione non hanno identificatore e sono identificati dal relativo elenco di parametri. Gli indicizzatori di estensione possono usare il set completo di funzionalità attualmente supportate dagli indicizzatori ordinari (corpi delle funzioni di accesso, membri con corpo di espressione, funzioni di accesso che restituiscono riferimenti, scoped parametri, attributi e così via).
Poiché gli indicizzatori sono sempre membri dell'istanza, un blocco di estensione che dichiara un indicizzatore deve fornire un parametro ricevitore denominato.
Le restrizioni esistenti sui membri dell'estensione continuano a essere applicate: gli indicizzatori all'interno di una dichiarazione di estensione non possono specificare abstract, sealedvirtualnewpartialoverrideprotected o uno dei modificatori di accessibilità correlati o init le funzioni di accesso.
public static class BitExtensions
{
extension(int i)
{
public bool this[int index]
{
get => ...;
}
}
}
Tutte le regole dello standard C# applicabili agli indicizzatori ordinari si applicano agli indicizzatori di estensione, ma i membri dell'estensione non dispongono di un oggetto implicito o esplicito this.
La regola di inferrità del membro di estensione esistente si applica ancora: per ogni membro dell'estensione non metodo, tutti i parametri di tipo del relativo blocco di estensione devono essere usati nel set combinato di parametri dell'estensione e del membro.
Attributo IndexerName
IndexerNameAttribute può essere applicato a un indicizzatore di estensioni. L'attributo non viene generato nei metadati, ma il relativo valore influisce sui conflitti tra i membri, determina il nome della proprietà e delle funzioni di accesso nei metadati e viene usato durante l'emissione [DefaultMemberAttribute] (vedere Metadati).
Consumption
Accesso all'indicizzatore
Le regole nell'accesso all'indicizzatore vengono aggiornate: se la normale elaborazione dell'accesso dell'indicizzatore non trova alcun indicizzatore applicabile, viene effettuato un tentativo di elaborare il costrutto come accesso all'indicizzatore di estensione.
- Tentare di eseguire l'associazione usando gli indicizzatori dell'istanza dichiarati (o ereditati) nel tipo di ricevitore. Se viene trovato un candidato applicabile, la risoluzione dell'overload seleziona tra i membri dell'istanza come oggi e si arresta.
- Tentare di eseguire l'associazione usando gli indicizzatori di istanza impliciti dichiarati (o ereditati) nel tipo di ricevitore. Se viene trovato un candidato applicabile, la risoluzione dell'overload seleziona tra i membri dell'istanza come oggi e si arresta.
- Tentare di eseguire l'associazione come accesso all'indicizzatore di estensione. Se questo processo trova un candidato applicabile, la risoluzione dell'overload seleziona tra i membri dell'estensione come descritto di seguito e si arresta.
- Tentare di eseguire l'associazione come accesso implicito dell'indicizzatore di estensione. Se questo processo trova un candidato applicabile, la risoluzione dell'overload seleziona tra i membri dell'estensione come descritto di seguito e si arresta.
Nota: la sezione di accesso agli elementi gestisce il caso in cui un argomento ha tipo dynamic, quindi non viene mai elaborato come accesso all'indicizzatore.
Accesso all'indicizzatore di estensione
I membri di estensione, inclusi gli indicizzatori di estensione, non vengono mai considerati quando il ricevitore è un'espressione base_access .
Nota: un element_access viene elaborato solo come accesso dell'indicizzatore se il ricevitore è una variabile o un valore, quindi gli indicizzatori di estensione non vengono mai considerati quando il ricevitore è un tipo.
Dato un element_accessE[A], l'obiettivo è identificare un indicizzatore di estensioni.
Un indicizzatore di estensione candidato è applicabile per quanto riguarda l'elenco A di argomenti e ricevitore E se una firma espansa, costituita dai parametri di tipo del blocco di estensione e da un elenco di parametri che combina il parametro di estensione con i parametri dell'indicizzatore, è applicabile rispetto a un elenco di argomenti che combina il ricevitore E con l'elenco Adi argomenti .
Viene riutilizzata la procedura dettagliata dell'ambito del metodo di estensione: vengono attraversati gli stessi ambiti consultati per la chiamata al metodo di estensione, inclusi gli ambiti lessicali e gli ambiti lessicali o usingusing static le importazioni.
Considerando ogni ambito a sua volta:
- I blocchi di estensione nelle dichiarazioni di classe statiche non generiche nell'ambito corrente vengono considerati.
- Gli indicizzatori in tali blocchi di estensione comprendono il set candidato.
- I candidati non accessibili vengono rimossi dal set.
- I candidati non applicabili (come definito in precedenza) vengono rimossi dal set.
- Se il set risultante di indicizzatori candidati è vuoto, si procede con l'ambito successivo o non si risolve l'accesso di un indicizzatore di estensione se è stato raggiunto l'ultimo ambito (si continuerà a tentare di risolvere come indicizzatore implicito di estensione in questo caso).
- In caso contrario, la risoluzione dell'overload viene applicata al set candidato. Se non è possibile identificare un singolo indicizzatore migliore, l'accesso dell'indicizzatore di estensione è ambiguo e si verifica un errore in fase di compilazione.
Usando questo singolo indicizzatore migliore identificato nel passaggio precedente, l'accesso all'indicizzatore viene quindi elaborato come chiamata al metodo statico.
A seconda del contesto in cui viene usato, l'accesso di un indicizzatore provoca la chiamata del get_accessor o del set_accessor dell'indicizzatore.
Se l'accesso all'indicizzatore è la destinazione di un'assegnazione, viene richiamato il metodo di implementazione statico set_accessor per assegnare un nuovo valore.
In tutti gli altri casi, il metodo di implementazione statico get_accessor viene richiamato per ottenere il valore corrente.
In entrambi i casi, la chiamata userà argomenti generici dedotti durante il controllo dell'applicabilità e il ricevitore come primo argomento.
Accesso implicito dell'indicizzatore di estensione
Un accesso implicito System.Index all'estensione (o System.Range) dell'indicizzatore è applicabile se:
- il element_access ha un singolo argomento di tipo
System.Index(oSystem.Range) e - una proprietà applicabile
LengthoCount(istanza o estensione) si trova nel tipo di ricevitore e - Un indicizzatore applicabile
this[int](oSlice(int, int)) (istanza o estensione) viene trovato nel tipo di ricevitore.
Altri moduli di accesso a elementi
Qualsiasi costrutto che rinvia all'associazione di accesso agli elementi (accesso o assegnazioni di elementi condizionali Null, assegnazioni di indici negli inizializzatori di oggetti o modelli di elenco e diffusione) partecipa automaticamente alla risoluzione dell'indicizzatore di estensione descritta in precedenza.
Alberi delle espressioni
Gli indicizzatori di estensione non possono essere acquisiti negli alberi delle espressioni.
Documentazione XML
La sintassi CREF consente di fare riferimento a un indicizzatore di estensioni e alle relative funzioni di accesso, nonché ai relativi metodi di implementazione.
Esempio:
/// <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
Gli indicizzatori di estensione seguono lo stesso modello di riduzione delle proprietà di estensione. Per ogni tipo di raggruppamento di estensioni a livello di CLR che contiene almeno un indicizzatore, il compilatore genera:
- Proprietà di estensione denominata
Item(o il valore fornito daIndexerNameAttribute) con corpi delle funzioni di accesso chethrow NotImplementedException()e un[ExtensionMarkerName]attributo che fa riferimento al tipo di marcatore di estensione appropriato. - Metodi di implementazione denominati
get_Item/set_Itemnella classe statica contenitore. Questi metodi anteponevano il parametro ricevitore all'elenco dei parametri e contengono i corpi definiti dall'utente. Sonostatice partecipano alla risoluzione dell'overload nello stesso modo dei metodi di implementazione per le proprietà di estensione.
Per eseguire il mirroring del comportamento degli indicizzatori ordinari, il compilatore genera [DefaultMemberAttribute] anche su qualsiasi tipo di raggruppamento di estensioni che contiene uno o più indicizzatori di estensione. L'attributo MemberName è uguale al nome dei metadati dell'indicizzatore (Item per impostazione predefinita o al valore di IndexerNameAttribute).
Example
Codice sorgente:
static class BitExtensions
{
extension<T>(T t)
{
public bool this[int index]
{
get => ...;
set => ...;
}
}
}
Metadati generati (semplificati con sintassi simile a C#):
[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) => ...;
}
Problemi aperti
Sezione temporanea del documento relativo ai problemi aperti, inclusa la discussione di progetti alternativi
Gestione dei problemi params
paramsSe si dispone di un indicizzatore di estensioni con params, ad esempio int this[int i, params string[] s] { get; set; }, è possibile usarlo in tre modi:
- indicizzazione dell'estensione:
receiver[i: 0, "Alice", "Bob"] - chiamata all'implementazione getter:
E.get_Item(receiver, i: 0, "Alice", "Bob") - chiamata all'implementazione del setter:
E.set_Item(...)
Ma qual è la firma del metodo di implementazione setter?
Ha senso solo per l'ultimo parametro di una firma del metodo invocabile dall'utente per avere params, quindi non serve alcuno scopo in E.set_Item(... extension parameter ..., this i, params string[] s, int value).
Alcune opzioni:
- non consentire
paramsagli indicizzatori di estensione con un setter - omettere l'attributo
[ParamArray]nel metodo di implementazione setter - non fare nulla di speciale (emettere il
[ParamArray])
Propongo l'opzione 2, in quanto massimizza l'utilità params . Il costo è solo una piccola differenza tra l'indicizzazione delle estensioni e la sintassi di disambiguazione.
Decisione (LDM 2026-02-02): generare [ParamArray] e verificare nessun impatto negativo sugli strumenti
Impatto del valore assegnato all'inferenza del tipo
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 { } }
}
}
Decisione (LDM 2026-02-02): l'indicizzatore viene dedotto solo in base al ricevitore e agli argomenti nell'elenco di argomenti (ad esempio, il valore assegnato non contribuisce).
Le proprietà dell'estensione Length/Count devono rendere un tipo conteggiabile?
Length/Count devono rendere un tipo conteggiabile?Come promemoria, le estensioni non vengono eseguite quando si associano indicizzatori di indice implicito o di intervallo:
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!;
}
La posizione finora è stata che le proprietà delle estensioni non devono essere conteggiate come "proprietà conteggiabili" in list-patterns, espressioni di raccolta e indicizzatori impliciti.
Se si espongono o this[Range] si espongono this[Index] indicizzatori di estensione in scenari di accesso agli elementi, è naturale prevedere che il tipo di destinazione funzioni nei modelli di elenco.
I modelli di elenco, tuttavia, richiedono una Length proprietà o Count .
Le proprietà dell'estensione devono soddisfare tale requisito? (che sarebbe naturale)
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 => ...;
}
}
Tuttavia, è necessario che queste proprietà contribuiscano anche al fallback implicito dell'indicizzatore (Length/ + SliceCount) usato quando manca un indicizzatore esplicito?Index/Range
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 => ...;
}
}
Decisione (LDM 2026-02-02): le estensioni devono contribuire ovunque, incluse le proprietà conteggiabili e il fallback implicito dell'indicizzatore.
Verificare se l'accesso all'indicizzatore di estensione precede o dopo gli indicizzatori impliciti
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] => ...;
}
}
Ho specificato e implementato l'accesso all'indicizzatore di estensione come priorità rispetto agli indicizzatori impliciti, ma ora pensano che dovrebbero venire dopo per evitare interruzioni di compatibilità non necessarie.
Aggiornamento (LDM 2026-02-02): questa operazione richiede ulteriori indagini. Sì, le estensioni dovrebbero venire dopo i membri non estensioni, ma oltre a ciò abbiamo bisogno di alcune proposte concrete alla luce della decisione precedente per consentire alle estensioni di contribuire al fallback implicito dell'indicizzatore.
Decisione (LDM 2026-03-09): l'ordine è indicizzatori di istanze reali, quindi indicizzatori di istanze impliciti, quindi indicizzatori di estensione reali, quindi indicizzatori di estensione impliciti. Fare riferimento a https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers
Conteggio/lunghezza: il nome è prioritario o non estensione o estensione?
È disponibile anche un fallback esistente: Length è prioritario rispetto alla Count proprietà .
Un'estensione Length deve provenire prima o dopo una proprietà non di estensione Count ?
Decisione (LDM 2026-03-09): si esaminerà l'ambito per ambito (a partire dall'ambito dell'istanza e procedendo negli ambiti di estensione) e all'interno di ogni ambito si cercherà prima Length e poi Count.
Fare riferimento a https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers
Confermare la progettazione proposta per gli indicizzatori impliciti
Si esamina l'ambito in base all'ambito, a partire dall'ambito dell'istanza e quindi si procede agli ambiti di estensione.
All'interno di un ambito:
- cerchiamo un indicizzatore reale,
- in caso contrario, se è presente un singolo argomento del tipo destro, si cerca un indicizzatore implicito.
Decisione (LDM 2026-03-09): no, quando ci spostiamo negli ambiti di estensione, useremo la risoluzione dell'estensione all-the-way-through.
Confermare la progettazione proposta per i modelli elenco
Per un modello di elenco con una distribuzione, si cercherà:
- a
Length/Count - un reale o implicito
this[Index] - un reale o implicito
this[Range]
Cercheremo ogni oggetto in modo indipendente.
Tuttavia, quando si cerca un oggetto implicito this[Index] o this[Range], le due parti devono provenire dallo stesso ambito:
Length/Countthis[int]/Slice(int, int)
Nota: la gestione non negativa per Length i modelli inizia quando un tipo può essere usato in un modello di elenco (ad esempio, è conteggiabile e indicizzato).
Decisione (LDM 2026-03-09): no, seguiremo invece questo ordine di ricerca:
- I modelli di elenco vengono risolti come se si cerca lunghezza/conteggio, indicizzatore di indici e indicizzatore Range singolarmente
- Per gli indicizzatori di indice e di intervallo, procedere come segue: a. Solo con la ricerca dell'istanza, trovare l'indice "reale" se possibile b. Solo con la ricerca dell'istanza, trovare le parti dell'indicizzatore implicito, se possibile c. Con la ricerca completa (instance+extension), trovare l'indice "reale" se possibile d. Con la ricerca completa (istanza+estensione), trovare le parti dell'indicizzatore implicito, se possibile (ognuna nelle singole ricerche)
Dovrebbe contribuire anche il metodo di estensione Slice ?
Slice ?_ = c[1..^1];
static class E
{
extension(C c)
{
public int Length => 3;
}
public static C Slice(this C c, int i, int j) => ...;
}
Decisione (LDM 2026-03-09): sì, stiamo trattando metodi di estensione classici e nuovi esattamente uguali.
L'estensione Length deve contribuire all'ottimizzazione della diffusione?
Length deve contribuire all'ottimizzazione della diffusione?C c = new C();
int[] i = [0, .. c]; // Uses Length, if available, to allocate the right size
Decisione (LDM 2026-03-09): no, è improbabile che un'estensione sia in grado di implementarla in modo efficiente, quindi non sarebbe utile per l'ottimizzazione.
Gli indicizzatori impliciti devono entrare in gioco per i tipi di matrice o stringa?
Si propone che gli indicizzatori impliciti non contribuiscano a questi scenari, perché sono possibili solo con un corlib in formato non valido (istanza Lengthmancante o GetSubArrayGetSubstring).
Decisione (LDM/Mads tramite posta elettronica 2026-04-07): nessun indicizzatore di estensione su stringhe o matrici.
Gli indicizzatori di estensione devono entrare in gioco per i tipi di matrice o stringa?
Le specifiche correnti e le regole di base per l'accesso alle matrici e all'accesso alle stringhe indicano che gli indicizzatori di estensione non funzionano su matrici o stringhe.
Tuttavia, la dichiarazione di tali indicizzatori di estensione è consentita.
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;
}
}
Nota: non esiste alcuna domanda per l'accesso agli elementi del puntatore perché un parametro di estensione potrebbe non essere un tipo di puntatore.
Decisione (LDM/Mads tramite posta elettronica 2026-04-07): nessun indicizzatore di estensione su stringhe o matrici.
C# feature specifications