Usando indexadores (Guia de Programação em C#)
Os indexadores são uma conveniência sintática que permite criar uma classe, struct ou interface que os aplicativos cliente podem acessar como uma matriz. O compilador gera uma Item
propriedade (ou uma propriedade com nome alternativo, se IndexerNameAttribute estiver presente) e os métodos de acesso apropriados. Os indexadores são mais frequentemente implementados em tipos cuja finalidade principal é encapsular uma coleção ou matriz interna. Por exemplo, suponha que você tenha uma classe TempRecord
que represente a temperatura em Fahrenheit registrada em 10 momentos diferentes durante um período de 24 horas. A classe contém uma temps
matriz de tipo float[]
para armazenar os valores de temperatura. Ao implementar um indexador nessa classe, os clientes podem acessar as temperaturas em uma TempRecord
instância como float temp = tempRecord[4]
em vez de como float temp = tempRecord.temps[4]
. A notação do indexador não apenas simplifica a sintaxe para aplicativos cliente; Isso também torna a classe e seu propósito mais intuitivos para outros desenvolvedores entenderem.
Para declarar um indexador em uma classe ou struct, use a palavra-chave this , como mostra o exemplo a seguir:
// Indexer declaration
public int this[int index]
{
// get and set accessors
}
Importante
Declarar um indexador gerará automaticamente uma propriedade nomeada Item
no objeto. A Item
propriedade não é diretamente acessível a partir da expressão de acesso do membro da instância. Além disso, se você adicionar sua própria Item
propriedade a um objeto com um indexador, obterá um erro de compilador CS0102. Para evitar esse erro, use o IndexerNameAttribute renomear o indexador conforme detalhado mais adiante neste artigo.
Observações
O tipo de indexador e o tipo de seus parâmetros devem ser pelo menos tão acessíveis quanto o próprio indexador. Para obter mais informações sobre níveis de acessibilidade, consulte Modificadores de acesso.
Para obter mais informações sobre como usar indexadores com uma interface, consulte Indexadores de interface.
A assinatura de um indexador consiste no número e tipos de seus parâmetros formais. Ele não inclui o tipo de indexador ou os nomes dos parâmetros formais. Se você declarar mais de um indexador na mesma classe, eles deverão ter assinaturas diferentes.
Um indexador não é classificado como uma variável; Portanto, um valor de indexador não pode ser passado por referência (como um ref
parâmetro OR out
), a menos que seu valor seja uma referência (ou seja, ele retorna por referência).
Para fornecer ao indexador um nome que outros idiomas possam usar, use System.Runtime.CompilerServices.IndexerNameAttribute, como mostra o exemplo a seguir:
// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
// get and set accessors
}
Este indexador tem o nome TheItem
, pois é substituído pelo atributo de nome do indexador. Por padrão, o nome do indexador é Item
.
Exemplo 1
O exemplo a seguir mostra como declarar um campo de matriz privada, temps
e um indexador. O indexador permite o acesso direto à instância tempRecord[i]
. A alternativa ao uso do indexador é declarar a matriz como um membro público e acessar seus membros, tempRecord.temps[i]
diretamente.
public class TempRecord
{
// Array of temperature values
float[] temps =
[
56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F
];
// To enable client code to validate input
// when accessing your indexer.
public int Length => temps.Length;
// Indexer declaration.
// If index is out of range, the temps array will throw the exception.
public float this[int index]
{
get => temps[index];
set => temps[index] = value;
}
}
Observe que quando o acesso de um indexador é avaliado, por exemplo, em uma Console.Write
instrução, o acessador get é invocado. Portanto, se nenhum get
acessador existir, ocorrerá um erro em tempo de compilação.
var tempRecord = new TempRecord();
// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;
// Use the indexer's get accessor
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}
Indexação usando outros valores
O C# não limita o tipo de parâmetro do indexador a inteiro. Por exemplo, pode ser útil usar uma cadeia de caracteres com um indexador. Esse indexador pode ser implementado pesquisando a cadeia de caracteres na coleção e retornando o valor apropriado. Como os acessadores podem ser sobrecarregados, as versões string e inteiro podem coexistir.
Exemplo 2
O exemplo a seguir declara uma classe que armazena os dias da semana. Um get
acessador usa uma cadeia de caracteres, o nome de um dia, e retorna o inteiro correspondente. Por exemplo, "Domingo" retorna 0, "Segunda-feira" retorna 1 e assim por diante.
// Using a string as an indexer value
class DayCollection
{
string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[string day] => FindDayIndex(day);
private int FindDayIndex(string day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
}
}
Exemplo de consumo 2
var week = new DayCollection();
Console.WriteLine(week["Fri"]);
try
{
Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
Exemplo 3
O exemplo a seguir declara uma classe que armazena os dias da semana usando o System.DayOfWeek enum. Um get
acessador usa um DayOfWeek
, o valor de um dia e retorna o número inteiro correspondente. Por exemplo, DayOfWeek.Sunday
retorna 0, DayOfWeek.Monday
retorna 1 e assim por diante.
using Day = System.DayOfWeek;
class DayOfWeekCollection
{
Day[] days =
[
Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
Day.Thursday, Day.Friday, Day.Saturday
];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[Day day] => FindDayIndex(day);
private int FindDayIndex(Day day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
}
}
Exemplo de consumo 3
var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);
try
{
Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
Programação robusta
Existem duas formas principais de melhorar a segurança e a fiabilidade dos indexadores:
Certifique-se de incorporar algum tipo de estratégia de tratamento de erros para lidar com a chance de o código do cliente passar em um valor de índice inválido. No primeiro exemplo anterior neste artigo, a classe TempRecord fornece uma propriedade Length que permite que o código do cliente verifique a entrada antes de passá-la para o indexador. Você também pode colocar o código de tratamento de erros dentro do próprio indexador. Certifique-se de documentar para os usuários quaisquer exceções que você lançar dentro de um acessador indexador.
Defina a acessibilidade dos acessadores get e set para ser tão restritiva quanto razoável. Isto é importante para o
set
acessor em particular. Para obter mais informações, consulte Restringindo a acessibilidade do Accessor.