Usando indexadores (Guia de Programação em C#)
Os indexadores são uma conveniência sintática que permite criar uma classe, um struct ou uma interface que os aplicativos clientes podem acessar como uma matriz. O compilador gera uma propriedade Item
(ou uma propriedade de nome alternativo, se IndexerNameAttribute estiver presente) e os métodos acessadores apropriados. Os indexadores são implementados em tipos cuja principal finalidade é encapsular uma coleção ou matriz interna. Por exemplo, suponha que você tenha uma classe TempRecord
que representa a temperatura em Fahrenheit, conforme registrada em 10 momentos diferentes durante um período de 24 horas. A classe contém uma matriz temps
do tipo float[]
para armazenar os valores de temperatura. Ao implementar um indexador nessa classe, os clientes podem acessar as temperaturas em uma instância TempRecord
como float temp = tempRecord[4]
, e não como float temp = tempRecord.temps[4]
. A notação do indexador não apenas simplifica a sintaxe para aplicativos clientes, mas também torna a classe e a finalidade dela mais intuitivas para que os outros desenvolvedores entendam.
Para declarar um indexador em uma classe ou struct, use a palavra-chave this, como mostra o seguinte exemplo:
// Indexer declaration
public int this[int index]
{
// get and set accessors
}
Importante
Declarar um indexador vai gerar automaticamente uma propriedade chamada Item
no objeto. A propriedade Item
não pode ser acessada diretamente por meio da expressão de acesso a membro da instância. Além disso, se você adicionar a sua propriedade Item
a um objeto com indexador, será gerado um erro do compilador CS0102. Para evitar esse erro, use a opção de IndexerNameAttribute renomear o indexador, conforme detalhado mais para frente neste artigo.
Comentários
O tipo de um indexador e o tipo dos 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 do número e dos tipos de seus parâmetros formais. Ela não inclui o tipo de indexador nem os nomes dos parâmetros formais. Se você declarar mais de um indexador na mesma classe, eles terão diferentes assinaturas.
Um indexador não é classificado como uma variável; portanto, um valor de indexador não pode ser passado por referência (como um parâmetro ref
ou out
), a menos que seu valor seja uma referência (ou seja, ele retorna por referência).
Para fornecer o indexador com um nome que outras linguagens possam usar, use System.Runtime.CompilerServices.IndexerNameAttribute, como mostra o seguinte exemplo:
// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
// get and set accessors
}
Esse indexador tem o nome TheItem
, pois ele é 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 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 instrução Console.Write
, o acessador get é invocado. Portanto, se não existir nenhum acessador get
, 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]}");
}
Indexando usando outros valores
O C# não limita o tipo de parâmetro do indexador ao 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 adequado. Como os acessadores podem ser sobrecarregados, as versões do inteiro e da cadeia de caracteres podem coexistir.
Exemplo 2
O exemplo a seguir declara uma classe que armazena os dias da semana. Um acessador get
aceita uma cadeia de caracteres, o nome de um dia e retorna o inteiro correspondente. Por exemplo, "Sunday" retorna 0, "Monday" 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 a enumeração System.DayOfWeek. Um acessador get
aceita DayOfWeek
, o valor de um dia, e retorna o 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
Há duas maneiras principais nas quais a segurança e a confiabilidade de indexadores podem ser melhoradas:
Certifique-se de incorporar algum tipo de estratégia de tratamento de erros para manipular a chance de passagem de código cliente em um valor de índice inválido. Anteriormente, no primeiro exemplo neste artigo, a classe TempRecord oferece uma propriedade Length que permite que o código cliente verifique a saída antes de passá-la para o indexador. Também é possível colocador o código de tratamento de erro dentro do próprio indexador. Certifique-se documentar para os usuários as exceções que você gera dentro de um acessador do indexador.
Defina a acessibilidade dos acessadores get e set para que ela seja mais restritiva possível. Isso é importante para o acessador
set
em particular. Para obter mais informações, consulte Restringindo a acessibilidade aos acessadores.