Partilhar via


Usando indexadores (Guia de Programação em C#)

Os indexadores são uma conveniência sintática que permite criar uma classe, estrutura ou interface que os aplicativos cliente podem acessar como uma matriz. O compilador irá gerar 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 abaixo.

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 terá o nome TheItem, uma vez que é 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, tempse 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 tópico, 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.

Consulte também