CodeGym /Cursos /C# SELF /Conhecendo os indexadores

Conhecendo os indexadores

C# SELF
Nível 18 , Lição 0
Disponível

1. Algumas palavras sobre coleções

Até agora a gente trabalhou com arrays — que é o jeito mais simples de guardar vários elementos do mesmo tipo. Mas no C# tem um monte de coleções bem mais práticas e poderosas, cada uma pra um tipo de situação diferente.

Coleção — é um objeto que pode guardar um grupo de outros objetos. Diferente dos arrays, as coleções geralmente podem mudar de tamanho dinamicamente, têm métodos práticos pra mexer nos dados e são otimizadas pra vários cenários de uso.

Principais tipos de coleções (resumão):

List<T> — array dinâmico, que pode crescer e diminuir:

List<string> nomes = new List<string>();
nomes.Add("Alex");
nomes.Add("Maria");
Console.WriteLine(nomes[0]); // Alex
Console.WriteLine(nomes.Count); // 2

Dictionary<TKey, TValue> — coleção de pares "chave-valor", onde cada chave tem um valor associado:

Dictionary<string, int> idades = new Dictionary<string, int>();
idades["Alex"] = 25;
idades["Maria"] = 30;
Console.WriteLine(idades["Alex"]); // 25

Repara só: nos exemplos acima a gente usa colchetes [] pra acessar elementos das coleções — igualzinho array! Isso rola por causa dos indexadores, que é o assunto de hoje.

Isso aqui é só o primeiro contato com coleções — depois a gente vai ver em detalhes as diferenças, possibilidades e usos. Por enquanto, o importante é sacar que coleções deixam você acessar elementos usando colchetes, e isso não é mágica, é um recurso da linguagem.

2. Indexadores

Objetos normais em C# funcionam com propriedades e métodos. Mas e se seu objeto for tipo uma mini-coleção? Imagina só:

  • Você cria uma classe Semana, que precisa devolver o nome do dia pelo número: semana[0] → "Segunda-feira".
  • Ou uma classe Biblioteca, onde dá pra pegar um livro pelo número: biblioteca[3] → "Em busca do tempo perdido".

Bem mais prático, né? Seria estranho ter que escrever biblioteca.GetLivroPorIndice(3) toda vez — a gente quer acessar o objeto como se fosse um array!

É aí que entram os indexadores.

Indexador — é um membro especial da classe que deixa você usar objetos dessa classe com sintaxe de colchetes, igual array: obj[0], obj["chave"], e por aí vai.

O indexador parece uma propriedade, mas em vez de nome, recebe parâmetros dentro dos colchetes. Tipo quando a gente escreve .Nome, só que aqui é [i].

3. Coleção simples com indexador

Bora montar uma mini-classe pra guardar cores favoritas. Vamos usar um array de strings. Sem indexador, teria que fazer um método tipo GetCor(int i). Mas com indexador:


using System;

public class CoresFavoritas
{
   // Campo privado pra guardar as cores
   private string[] cores = new string[5];

   // Indexador:
   public string this[int indice]
   {
       get
       {
           // Checa os limites do array (encapsulamento na prática!)
           if (indice < 0 || indice >= cores.Length)
               throw new IndexOutOfRangeException("Índice de cor inválido!");

           return cores[indice];
       }
       set
       {
           if (indice < 0 || indice >= cores.Length)
               throw new IndexOutOfRangeException("Índice de cor inválido!");

           cores[indice] = value ?? throw new ArgumentNullException(nameof(value));
       }
   }
}

class Program
{
   static void Main()
   {
       CoresFavoritas favoritas = new CoresFavoritas();

       favoritas[0] = "Verde";
       favoritas[1] = "Azul";
       favoritas[2] = "Vermelho";
       favoritas[10] = "Violeta"; // Lança exceção!

       Console.WriteLine(favoritas[1]); // Azul
   }
}

O que tá rolando aqui?

  • A gente cria um array privado, pra ninguém mexer direto de fora.
  • O indexador é definido como public string this[int indice], onde this indica que o indexador é do objeto.
  • No get e set a gente checa os limites, não deixa passar do array ou gravar null.
  • No fim, dá pra fazer favoritas[0], igual array normal.

4. Sintaxe do indexador: detalhes

A sintaxe parece uma propriedade, mas no lugar do nome (tipo Idade) vai a palavra this com parâmetros:


// Assinatura do indexador (modelo geral)
[modificador] TipoDeRetorno this[TipoIndice nomeIndice]
{
   get { ... }
   set { ... }
}

Exemplo: Clássico

public class MinhaColecao
{
   private int[] dados = new int[10];

   // Indexador pra leitura e escrita
   public int this[int indice]
   {
       get { return dados[indice]; }
       set { dados[indice] = value; }
   }
}

Indexadores não precisam ser só por int

O legal é que o indexador não precisa aceitar só int. Você pode usar qualquer tipo (desde que faça sentido acessar por essa chave):

public string this[string nomeCor]
{
   get { /* ... */ }
   set { /* ... */ }
}

Por exemplo, numa agenda telefônica faz sentido buscar pelo nome:

public class AgendaTelefonica
{
   private Dictionary<string, int> contatos = new Dictionary<string, int>();

   public int this[string nome]
   {
       get
       {
           if (contatos.ContainsKey(nome))
               return contatos[nome];
           return null;
       }
       set
       {
           contatos[nome] = value;
       }
   }
}

Sobre coleções e como funciona o Dictionary<string, string> eu explico nas próximas aulas :P

5. Exemplo prático: Contador de palavras em texto

Vamos evoluir nosso app. Agora temos uma classe que conta quantas vezes cada palavra aparece no texto. Fica fácil se o usuário puder acessar pelo colchete pra pegar a contagem de uma palavra:


using System.Collections.Generic;

public class ContadorDePalavras
{
   private Dictionary<string, int> contador = new Dictionary<string, int>();

   // Indexador por string (palavra)
   public int this[string palavra]
   {
       get
       {
           if (contador.ContainsKey(palavra))
               return contador[palavra];
           return 0; // Se não tem a palavra, retorna 0.
       }
       set
       {
           contador[palavra] = value;
       }
   }

   // Método pra contar palavras de uma string
   public void AdicionarPalavras(string texto)
   {
       foreach (var palavra in texto.Split(' ', System.StringSplitOptions.RemoveEmptyEntries))
       {
           if (contador.ContainsKey(palavra))
               contador[palavra]++;
           else
               contador[palavra] = 1;
       }
   }
}

// No Main:
var cp = new ContadorDePalavras();
cp.AdicionarPalavras("mama lavou janela lavou mama papa");
Console.WriteLine($"'mama' aparece {cp["mama"]} vez(es)");
Console.WriteLine($"'janela' aparece {cp["janela"]} vez(es)");
Console.WriteLine($"'gato' aparece {cp["gato"]} vez(es)"); // 0

Pra que serve isso na prática? Esse jeito é muito usado pra criar coleções próprias, bibliotecas de memória, mapeamentos (dicionários e índices) e até DSL (linguagens especializadas dentro do C#).

6. Limitações e detalhes

Indexadores são poderosos, mas tem umas regras e pegadinhas (sempre tem, né?).

Indexador não tem nome

Diferente de propriedade, indexador não tem nome, só a assinatura tipo this[tipo do parâmetro]. Se você tentar public int MeuIndexador[int i] — o compilador vai reclamar. Só usa this mesmo.

Não existe indexador estático

Indexadores sempre são pra instância da classe, nunca pra membros estáticos. Não dá pra declarar static int this[int i] — porque this sempre aponta pra um objeto específico.

Pode ter sobrecarga por tipo/quantidade de parâmetros

Dá pra criar vários indexadores na mesma classe, se os parâmetros forem de tipos ou quantidades diferentes. Tipo:

public string this[int i] { get { ... } set { ... } }
public string this[string chave] { get { ... } set { ... } }

Isso é de boa, o compilador entende — e te avisa se os parâmetros forem iguais.

Só funciona com get/set

Não dá pra declarar indexador sem os acessores get ou set. Se quiser só leitura — tira o set, só escrita — tira o get. Normalmente usa os dois.

7. Por que isso é útil e por que aprender

  • Indexadores são usados direto em coleções de dados. Vários tipos do .NET têm: tipo List<T>, Dictionary<TKey,TValue>. Quando você faz lista[2], tá usando indexador!
  • Indexadores escondem a implementação interna (encapsulamento!), mas dão uma interface prática e familiar. Quem usa sua classe nem liga pra como você guarda os dados, só usa os [indice] de sempre.
  • Seu código fica mais limpo e intuitivo — o que a galera adora em entrevista (e seus futuros colegas também).

Propriedades vs Indexadores: comparação

Propriedade Indexador
Nome Sim (tipo, Nome) Não (em vez de nome — this[parâmetro])
Acesso Pelo nome Pelo índice (ou outra chave)
Estático Pode ser static Só pra instância
Vários na classe Sim, quantos quiser Sim, mas com assinatura diferente
Uso Guardar/acessar dados Mini-coleções, dados associativos

8. Erros comuns e dicas

Erro nº1: tentar fazer indexador static.
Não rola — this[...] só funciona com objeto.

Erro nº2: esqueceu de checar o índice.
Se não checar os limites do array no get ou set, o programa pode dar crash.

Erro nº3: confundiu tipos dos parâmetros.
Se criar dois indexadores com os mesmos parâmetros, o compilador vai dar erro.

Erro nº4: esqueceu de implementar get ou set.
Se quiser acesso de leitura e escrita — tem que ter os dois.

Dica: se sua classe encapsula um array — só repassa o acesso pelo indexador. Isso deixa tudo mais rápido e intuitivo.

9. Por que aprender isso tudo

Indexadores deixam a interface do objeto simples, clara e prática. Eles escondem a implementação interna, mas deixam o dev acessar os dados de um jeito fácil.

É assim que funcionam as coleções nativas: string, List<T>, Dictionary<TKey, TValue>, Span<T> e várias outras. Quando você faz array[2] ou texto[0], já tá usando indexador.

O mais massa — agora você pode criar suas próprias classes que funcionam desse jeito flexível e limpo. Ou seja — você tá um passo mais perto de escrever código profissional e fácil de ler.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION