Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Note
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ela inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas divergências entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas LDM (reunião de design de idioma) pertinentes.
Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações.
Problema do especialista: https://github.com/dotnet/csharplang/issues/9856
Declaração
Gramática
Os indexadores de extensão são adicionados ao conjunto de membros permitidos dentro de uma declaração de extensão estendendo a gramática da seguinte maneira (em relação a propostas/csharp-14.0/extensions.md):
extension_member_declaration
: method_declaration
| property_declaration
| indexer_declaration // new
| operator_declaration
;
Assim como os indexadores comuns, os indexadores de extensão não têm identificador e são identificados pela lista de parâmetros. Os indexadores de extensão podem usar o conjunto completo de recursos que os indexadores comuns dão suporte hoje (corpos do acessador, membros aptos à expressão, acessadores de retorno de ref, scoped parâmetros, atributos etc.).
Como os indexadores são sempre membros da instância, um bloco de extensão que declara um indexador deve fornecer um parâmetro de receptor nomeado.
As restrições existentes aos membros da extensão continuam a ser aplicadas: indexadores dentro de uma declaração de extensão não podem especificarabstract, virtual, override, new, sealed, partialprotected (ou qualquer um dos modificadores de acessibilidade relacionados) ou init acessadores.
public static class BitExtensions
{
extension(int i)
{
public bool this[int index]
{
get => ...;
}
}
}
Todas as regras do padrão C# que se aplicam a indexadores comuns se aplicam aos indexadores de extensão, mas os membros da extensão não têm um implícito ou explícito this.
A regra de inferência de membro de extensão existente ainda se aplica: para cada membro de extensão não método, todos os parâmetros de tipo de seu bloco de extensão devem ser usados no conjunto combinado de parâmetros da extensão e do membro.
Atributo IndexerName
IndexerNameAttribute pode ser aplicado a um indexador de extensão. O atributo não é emitido em metadados, mas seu valor afeta conflitos entre membros, determina o nome da propriedade e acessadores nos metadados e é usado ao emitir [DefaultMemberAttribute] (consulte Metadados).
Consumo
Acesso de indexador
As regras no acesso ao Indexador são atualizadas: se o processamento normal do acesso do indexador não encontrar nenhum indexador aplicável, será feita uma tentativa de processar o constructo como um acesso do indexador de extensão.
- Tente associar usando os indexadores de instância declarados (ou herdados) no tipo receptor. Se um candidato aplicável for encontrado, a resolução de sobrecarga selecionará entre esses membros da instância como hoje e será interrompida.
- Tente associar usando os indexadores de instância implícita declarados (ou herdados) no tipo receptor. Se um candidato aplicável for encontrado, a resolução de sobrecarga selecionará entre esses membros da instância como hoje e será interrompida.
- Tente associar como um acesso de indexador de extensão. Se esse processo encontrar um candidato aplicável, a resolução de sobrecarga selecionará entre esses membros de extensão, conforme descrito abaixo e será interrompido.
- Tente associar como um acesso de indexador implícito de extensão. Se esse processo encontrar um candidato aplicável, a resolução de sobrecarga selecionará entre esses membros de extensão, conforme descrito abaixo e será interrompido.
Observação: a seção de acesso ao elemento manipula o caso em que um argumento tem tipo dynamic, portanto, ele nunca é processado como um acesso de indexador.
Acesso ao indexador de extensão
Os membros da extensão, incluindo indexadores de extensão, nunca são considerados quando o receptor é uma expressão base_access .
Observação: processaremos apenas um element_access como um acesso de indexador se o receptor for uma variável ou valor, portanto, os indexadores de extensão nunca serão considerados quando o receptor for um tipo.
Dado um element_accessE[A], o objetivo é identificar um indexador de extensão.
Um indexador de extensão candidato é aplicável em relação ao receptor E e à lista A de argumentos se uma assinatura expandida, composta pelos parâmetros de tipo do bloco de extensão e uma lista de parâmetros que combina o parâmetro de extensão com os parâmetros do indexador, for aplicável em relação a uma lista de argumentos que combina o receptor E com a lista Ade argumentos.
Reutilizamos a caminhada de escopo do método de extensão: percorremos os mesmos escopos consultados para invocação do método de extensão, incluindo os escopos léxicos atuais e delimitados e using o namespace ou using static as importações.
Considerando cada escopo, por sua vez:
- Blocos de extensão em declarações de classe estática não genéricas no escopo atual são considerados.
- Os indexadores nesses blocos de extensão compõem o conjunto de candidatos.
- Os candidatos que não estão acessíveis são removidos do conjunto.
- Os candidatos que não são aplicáveis (conforme definido acima) são removidos do conjunto.
- Se o conjunto resultante de indexadores candidatos estiver vazio, prosseguiremos para o próximo escopo ou não resolveremos um acesso de indexador de extensão se atingirmos o último escopo (continuaremos tentando resolver como um indexador implícito de extensão nesse caso).
- Caso contrário, a resolução de sobrecarga será aplicada ao conjunto de candidatos. Se um único melhor indexador não puder ser identificado, o acesso ao indexador de extensão será ambíguo e ocorrerá um erro de tempo de compilação.
Usando este único melhor indexador identificado na etapa anterior, o acesso do indexador é processado como uma invocação de método estático.
Dependendo do contexto no qual ele é usado, um acesso de indexador causa invocação do get_accessor ou do set_accessor do indexador.
Se o acesso do indexador for o destino de uma atribuição, o método de implementação estático set_accessor será invocado para atribuir um novo valor.
Em todos os outros casos, o método de implementação estático get_accessor é invocado para obter o valor atual.
De qualquer forma, a invocação usará argumentos genéricos inferidos durante a verificação de aplicabilidade e o receptor como o primeiro argumento.
Acesso ao indexador implícito de extensão
Um acesso de indexador implícito System.Index de extensão (ou System.Range) será aplicável se:
- o element_access tem um único argumento que é do tipo
System.Index(ouSystem.Range), e - uma propriedade aplicável
LengthouCount(instância ou extensão) é encontrada no tipo de receptor e - um indexador aplicável
this[int](ouSlice(int, int)) (instância ou extensão) é encontrado no tipo de receptor.
Outros formulários de acesso a elementos
Qualquer construção que adie a associação de acesso a elementos (acesso ou atribuições de elemento condicional nulo, atribuições de índice em inicializadores de objeto ou padrões de lista e distribuição) participa automaticamente da resolução do indexador de extensão descrita acima.
Árvores de expressão
Os indexadores de extensão não podem ser capturados em árvores de expressão.
Documentos XML
A sintaxe CREF permite fazer referência a um indexador de extensão e seus acessadores, bem como seus métodos de implementação.
Exemplo:
/// <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
Os indexadores de extensão seguem o mesmo modelo de redução que as propriedades de extensão. Para cada tipo de agrupamento de extensão de nível CLR que contém pelo menos um indexador, o compilador emite:
- Uma propriedade de extensão chamada
Item(ou o valor fornecido porIndexerNameAttribute) com corpos acessadores que e um[ExtensionMarkerName]atributo referenciandothrow NotImplementedException()o tipo de marcador de extensão apropriado. - Métodos de implementação nomeados
get_Item/set_Itemna classe estática delimitada. Esses métodos anexam o parâmetro receptor à lista de parâmetros e contêm os corpos definidos pelo usuário. Eles sãostatice participam da resolução de sobrecarga da mesma forma que os métodos de implementação para propriedades de extensão.
Para espelhar o comportamento dos indexadores comuns [DefaultMemberAttribute] , o compilador também emite qualquer tipo de agrupamento de extensão que contenha um ou mais indexadores de extensão. O atributo é MemberName igual ao nome de metadados do indexador (Item por padrão, ou o valor de IndexerNameAttribute).
Example
Código-fonte:
static class BitExtensions
{
extension<T>(T t)
{
public bool this[int index]
{
get => ...;
set => ...;
}
}
}
Metadados emitidos (simplificados para sintaxe do tipo 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) => ...;
}
Problemas em aberto
Seção temporária do documento relacionada a problemas abertos, incluindo discussão de designs alternativos
Lidando com params
paramsSe você tiver um indexador de extensão com params, por int this[int i, params string[] s] { get; set; }exemplo, há três maneiras de usá-lo:
- indexação de extensão:
receiver[i: 0, "Alice", "Bob"] - invocação de implementação getter:
E.get_Item(receiver, i: 0, "Alice", "Bob") - invocação de implementação do setter:
E.set_Item(...)
Mas qual é a assinatura do método de implementação do setter?
Só faz sentido que o último parâmetro de uma assinatura de método invocado pelo usuário tenha params, portanto, ele não serve para nenhuma finalidade em E.set_Item(... extension parameter ..., this i, params string[] s, int value).
Algumas opções:
- não permitir
paramspara indexadores de extensão que têm um setter - omita o
[ParamArray]atributo no método de implementação do setter - não fazer nada especial (emitir o
[ParamArray])
Eu proporia a opção 2, pois maximiza a params utilidade. O custo é apenas uma pequena diferença entre a indexação de extensão e a sintaxe de desambiguação.
Decisão (LDM 2026-02-02): emita e [ParamArray] verifique se não há impacto negativo nas ferramentas
Impacto do valor atribuído à inferência de 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 { } }
}
}
Decisão (LDM 2026-02-02): o indexador é inferido apenas considerando o receptor e os argumentos na lista de argumentos (ou seja, o valor atribuído não contribui).
As propriedades de extensão Length/Count devem tornar um tipo contível?
Length/Count devem tornar um tipo contível?Como lembrete, as extensões não entram em jogo ao associar indexadores implícitos de Índice ou Intervalo:
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!;
}
Portanto, nossa posição até agora foi de que as propriedades de extensões não devem contar como "propriedades contagem" em padrões de lista, expressões de coleção e indexadores implícitos.
Se expormos this[Index] ou this[Range] os indexadores de extensão em cenários de acesso a elementos, é natural esperar que o tipo de destino funcione em padrões de lista.
Os padrões de lista, no entanto, exigem uma propriedade ou Count uma Length propriedade.
As propriedades de extensão devem atender a esse requisito? (que parece natural)
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 => ...;
}
}
Mas, então, essas propriedades também devem contribuir para o fallback do indexador implícito (Length + Slice/Count) usado quando um indexador explícito Index/Range está ausente?
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 => ...;
}
}
Decisão (LDM 2026-02-02): as extensões devem contribuir em todos os lugares, incluindo propriedades contáveis e fallback implícito do indexador.
Confirmar se o acesso ao indexador de extensão vem antes ou depois de indexadores implícitos
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] => ...;
}
}
Eu especifiquei e implementei o acesso ao indexador de extensão como tendo prioridade sobre indexadores implícitos, mas agora acho que eles devem vir depois para evitar quebras de compatibilidade desnecessárias.
Atualização (LDM 2026-02-02): isso precisa de uma investigação mais aprofundada. Sim, as extensões devem vir após membros não extensões, mas além disso, precisamos de algumas propostas concretas à luz da decisão acima para permitir que as extensões contribuam para o fallback implícito do indexador.
Decisão (LDM 2026-03-09): a ordem é indexadores de instância real, indexadores de instância implícita, indexadores de extensão reais e indexadores de extensão implícitos. Consulte https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers
Contagem/Comprimento: o nome é priorizado primeiro ou não é extensão versus extensão?
Também temos um fallback existente: Length é priorizado em relação Count à propriedade.
Uma extensão Length deve vir antes ou depois de uma propriedade que não seja de extensão Count ?
Decisão (LDM 2026-03-09): examinaremos escopo por escopo (começando no escopo da instância e continuando por escopos de extensão) e dentro de cada escopo procuraremos Length primeiro e depois Count.
Consulte https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md#extension-indexers
Confirmar o design proposto para indexadores implícitos
Procuramos escopo por escopo, começando do escopo da instância e, em seguida, continuando para escopos de extensão.
Dentro de um escopo:
- procuramos um indexador real,
- caso contrário, se tivermos um único argumento do tipo certo, procuraremos um indexador implícito.
Decisão (LDM 2026-03-09): não, depois de passarmos para escopos de extensão, usaremos a resolução de extensão de todo o caminho.
Confirmar o design proposto para padrões de lista
Para um padrão de lista com um spread, procuraremos:
- a
Length/Count - um real ou implícito
this[Index] - um real ou implícito
this[Range]
Procuraremos cada um de forma independente.
Mas quando procuramos um implícito this[Index] ou this[Range], as duas partes devem vir do mesmo escopo:
Length/Countthis[int]/Slice(int, int)
Observação: o tratamento não negativo para Length padrões é inicializado quando um tipo pode ser usado em um padrão de lista (ou seja, é contagem e indexável).
Decisão (LDM 2026-03-09): não, seguiremos esta ordem de pesquisa em vez disso:
- Os padrões de lista são resolvidos como se procuramos por comprimento/contagem, indexador de índice e indexador de intervalo individualmente
- Para indexadores de Índice e Intervalo, prossiga da seguinte maneira: a. Somente com a pesquisa de instância, localize o índice "real", se possível b. Somente com a pesquisa de instância, localize as partes do indexador implícito, se possível c. Com a pesquisa completa (instância+extensão), localize o índice "real", se possível d. Com a pesquisa completa (instância+extensão), localize as partes do indexador implícito, se possível (cada uma em pesquisas individuais)
O método de extensão Slice também deve contribuir?
Slice também deve contribuir?_ = c[1..^1];
static class E
{
extension(C c)
{
public int Length => 3;
}
public static C Slice(this C c, int i, int j) => ...;
}
Decisão (LDM 2026-03-09): sim, estamos tratando métodos de extensão clássicos e novos exatamente iguais.
A extensão Length deve contribuir para a otimização de propagação?
Length deve contribuir para a otimização de propagação?C c = new C();
int[] i = [0, .. c]; // Uses Length, if available, to allocate the right size
Decisão (LDM 2026-03-09): não, é improvável que uma extensão seja capaz de implementar isso de maneira performativa, portanto, não ajudaria na otimização.
Os indexadores implícitos devem entrar em jogo para tipos de matriz ou cadeia de caracteres?
Proponho que os indexadores implícitos não contribuam para esses cenários, pois eles só são possíveis com um corlib malformado (instância Lengthausente ou GetSubArrayGetSubstring).
Decisão (LDM/Mads por email 2026-04-07): nenhum indexador de extensão em cadeias de caracteres ou matrizes.
Os indexadores de extensão devem entrar em jogo para tipos de matriz ou cadeia de caracteres?
As regras de especificação e linha de base atuais para acesso à matriz e acesso à cadeia de caracteres significam que os indexadores de extensão não funcionam em matrizes ou cadeias de caracteres.
No entanto, a declaração desses indexadores de extensão é permitida.
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;
}
}
Observação: não há dúvida sobre o acesso ao elemento ponteiro , pois um parâmetro de extensão pode não ser um tipo de ponteiro.
Decisão (LDM/Mads por email 2026-04-07): nenhum indexador de extensão em cadeias de caracteres ou matrizes.
C# feature specifications