Trabalhando com indexadores em C#

Veja neste artigo como trabalhar com indexadores na linguagem C#, encapsulando o acesso a listas em classes próprias, como acontece com classes como List e Hashtable e com vetores.

Introdução

Uma das estruturas de dados mais comumente utilizadas no desenvolvimento de software é o vetor, ou array. Essa estrutura consiste em uma coleção de elementos de um determinado tipo, definido no momento da sua declaração.

Uma vez instanciado e preenchido, os elementos do vetor podem ser acessados a partir de um índice, que indica sua posição na lista, geralmente começando a contar a partir do zero (zero-based).

Para acessar esses elementos, não só em C# como em outras linguagens, utiliza-se o nome do vetor, seguido de colchetes ([ e ]), dentro dos quais se insere o índice desejado, como no exemplo abaixo.

Listagem 1: Exemplo de acesso aos elementos de um vetor em C#

string[] nomes = new string[3]; nomes[0] = "João"; nomes[1] = "Maria"; nomes[2] = "José"; string nomeSelecionado = nomes[2];

Como vemos, tanto para definir quanto para obter um elemento, utilizamos a mesma sintaxe: nomes[índice]. É dessa forma que funcionam os indexadores, facilitando o acesso aos elementos da lista através do uso desse formato (colchetes com o índice).

Comportamento semelhante é observado em classes de coleção como List e Hashtable. Esta última, por exemplo, representa uma lista de objetos, onde cada elemento possui uma chave que o identifica e um valor.

Para acessar os elementos da coleção, também se utiliza um indexador. Porém, nesse caso, o indexador não recebe exatamente o índice (posição) do elemento que se deseja acessar, mas recebe a chave deste. A compreensão fica mais fácil com um exemplo, apresentado na listagem 2.

Listagem 2: Indexador na classe Hashtable

Hashtable clientes = new Hashtable(); clientes.Add("001", "João"); clientes.Add("002", "Maria"); clientes.Add("003", "José"); var clienteSelecionado = clientes["001"];

Novamente aparecem os colchetes, dentro dos quais informamos a chave do elemento que queremos acessar. Com isso percebemos que os indexadores nos permitem acessar elementos membros de uma coleção a partir de uma determinada característica única, como um índice ou uma chave.

Diante dessa facilidade, nos perguntamos: é possível criar indexadores nas minhas próprias classes? Conhecendo o poder do .NET Framework e da linguagem C#, a resposta não poderia ser outra: SIM. Podemos criar indexadores e essa é uma tarefa consideravelmente simples, como veremos a seguir.

A sintaxe para definição de um indexador é a seguinte:

Listagem 3: Sintaxe de criação de indexadores

Tipo_Retorno this[Tipo_Indice índice] { get{}; set{}; }

Tipo_Retorno é o tipo do objeto que será acessado pelo indexador, geralmente o mesmo tipo de elemento contido em uma lista.

Tipo_Indice é o tipo de dado do índice a ser utilizado. No caso da Hashtable, por exemplo, esse tipo é object, pois é o tipo das chaves dos elementos da lista.

Vejamos um exemplo prático de uso de indexadores para ajudar a fixar essa sintaxe.

Criando indexadores em classes próprias

Tomemos então, como situação problema para exemplificar o uso de indexadores, uma aplicação de vendas onde temos uma classe Pedido na qual inserimos itens, podendo acessá-los posteriormente.

Um dos requisitos para a construção dessa classe é que ela deve conter uma lista interna de itens, mas essa lista não deve poder ser acessada diretamente por classes externas. Deve-se poder inserir e acessar os valores, mas não alterar a lista diretamente. Para atender a essa regra, vamos criar um método para adicionar itens e um indexador para acessá-los a partir do índice na lista.

Então, mãos à obra. Vamos criar uma aplicação console, uma vez que layout e interface não são questões relevantes para este artigo.

Com a aplicação criada, vamos adicionar uma classe, clicando com o botão direto do mouse sobre o nome do projeto e, em seguida, em Add > Class. Essa primeira classe deverá se chamar Item, pois representará os produtos que são inseridos no pedido. A listagem 4 apresenta o código completo dessa classe.

Listagem 4: Classe Item

public class Item { private string _descricao; private decimal _quantidade; private decimal _subtotal; public string Descricao { get { return _descricao; } set { _descricao = value; } } public decimal Quantidade { get { return _quantidade; } set { _quantidade = value; } } public decimal Subtotal { get { return _subtotal; } set { _subtotal = value; } } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.AppendLine("---"); sb.AppendLine("Descrição: " + this._descricao); sb.AppendLine("Quantidade: " + this._quantidade.ToString("#,##0.00")); sb.AppendLine("Subtotal: " + this._subtotal.ToString("#,##0.00")); sb.AppendLine("---"); return sb.ToString(); } }

Observação: o método ToString foi sobrescrito para que possamos exibir o item no console de forma mais amigável.

Em seguida, vamos adicionar uma classe chamada Pedido, que conterá uma lista de objetos do tipo Item, conforme vemos na listagem 5.

Listagem 5: Classe Pedido

public class Pedido { private List<Item> _itens; public Pedido() { _itens = new List<Item>(); } public void AdicionarItem(Item item) { this._itens.Add(item); } public Item this[int indice] { get { if (indice < this._itens.Count) return this._itens[indice]; else throw new IndexOutOfRangeException("O índice está fora dos limites."); } set { this._itens[indice] = value; } } }

Conforme o requisito, temos um método para inserir itens no pedido e um indexador para acessá-los posteriormente. A lista em si é privada, não podendo ser acessada diretamente por classes externas.

O indexador é composto por métodos get e set, que representam a definição e acesso aos elementos. Assim como as propriedades “comuns”, os indexadores também podem ser apenas para leitura ou apenas para definição, bastando remover o método get ou set, de acordo com a necessidade. Neste caso, queremos permitir que as classes externas acessem os itens da lista e os alterem, atribuindo-lhes um novo valor.

O tipo de retorno do indexador, como era de se esperar, é Item, o tipo dos elementos da lista _itens.

Para testar o funcionamento dessa estrutura, vamos à classe Program, no método Main e criemos um pedido com alguns itens, como mostra a listagem 5.

Listagem 5: Utilizando a classe com indexador

static void Main(string[] args) { try { Pedido pedido = new Pedido(); pedido.AdicionarItem(new Item() { Descricao = "Monitor LCD 21''", Quantidade = 1, Subtotal = 400 }); pedido.AdicionarItem(new Item() { Descricao = "Mouse Optico", Quantidade = 2, Subtotal = 98 }); pedido.AdicionarItem(new Item() { Descricao = "Gabinete Gamer", Quantidade = 1, Subtotal = 750 }); Console.WriteLine("O pedido tem 3 itens. Qual você deseja acessar?"); int indice = int.Parse(Console.ReadLine()); Console.WriteLine(pedido[indice].ToString()); } catch(Exception ex) { Console.WriteLine("ERRO: " + ex.Message); } Console.ReadLine(); }

Inicialmente criamos um objeto do tipo Pedido e adicionamos três itens. Em seguida, solicitamos ao usuário o índice de um elemento que ele deseje acessar. Caso esse índice esteja dentro dos limites, o item será exibido no console, caso contrário, uma exceção será gerada, conforme definimos no getter do indexador.

Vamos então executar a aplicação e informar um índice válido.


Figura 1: Elemento acessado pelo indexador

Repetindo o teste, mas informando um índice fora dos limites, teremos uma

exceção.

Figura 2: Exceção gerada pelo indexador

O objetivo deste exemplo foi apresentar os indexadores na linguagem C#, de forma prática e bem próxima de uma situação real, mas ao mesmo tempo, de uma maneira simples que facilite a compreensão.

Fica como sugestão para o leitor, alterar o tipo do índice recebido pelo indexador e definir outras regras para o método de acesso (get), explorando todos as possibilidades oferecidas por este poderoso recurso.

Finalizamos então este artigo. Até a próxima oportunidade.

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados