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.