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.
GO TO FULL VERSION