C#: Coleções e genéricos
1. Conceitos gerais e motivação
Os genéricos nos permitem escrever código parametrizado por tipos, garantindo segurança de tipos e reutilização de código. Antes dos genéricos, ArrayList armazenava tipos object; os tipos de valor eram encapsulados ao serem adicionados e desencapsulados ao serem recuperados, o que prejudicava o desempenho e não permitia a verificação de tipos em tempo de compilação.
▶ Exemplo
using System;
using System.Collections;
ArrayList list = new ArrayList();
list.Add(42);
list.Add("hello");
int value = (int)list[0];
foreach (object item in list)
{
Console.WriteLine(item);
}
42
hello
A versão genérica determina os tipos dos elementos em tempo de compilação, sem a necessidade de boxing ou unboxing, e os erros são detectados durante a compilação:
using System;
using System.Collections.Generic;
List````<int>```` numbers = new List````<int>````();
numbers.Add(42);
foreach (int n in numbers)
{
Console.WriteLine(n);
}
42
2. Lista <T> Lista dinâmica
List<T> é a coleção genérica mais utilizada, oferecendo suporte a uma lista ordenada com tamanho redimensionável dinamicamente e dotada de diversos métodos para adicionar, remover, pesquisar e modificar elementos.
▶ Exemplo
using System;
using System.Collections.Generic;
List````<string>```` fruits = new List````<string>```` { "apple", "banana", "cherry" };
fruits.Add("date");
fruits.AddRange(new string[] { "elderberry", "fig" });
fruits.Insert(1, "blueberry");
bool hasApple = fruits.Contains("apple");
int idx = fruits.IndexOf("cherry");
string found = fruits.Find(f => f.StartsWith("b"));
List````<string>```` allB = fruits.FindAll(f => f.StartsWith("b"));
fruits.Sort();
fruits.ForEach(f => Console.WriteLine(f));
Console.WriteLine("---");
Console.WriteLine($"Count: {fruits.Count}");
Console.WriteLine($"Has apple: {hasApple}");
Console.WriteLine($"Cherry index: {idx}");
Console.WriteLine($"First b-word: {found}");
Console.WriteLine($"All b-words: {string.Join(", ", allB)}");
apple
blueberry
banana
cherry
date
elderberry
fig
---
Count: 7
Has apple: True
Cherry index: 3
First b-word: blueberry
All b-words: blueberry, banana
(1) Remoção de elementos
using System;
using System.Collections.Generic;
List````<int>```` nums = new List````<int>```` { 10, 20, 30, 40, 50 };
nums.Remove(30);
nums.RemoveAt(0);
Console.WriteLine(string.Join(", ", nums));
Console.WriteLine($"Count: {nums.Count}");
20, 40, 50
Count: 3
3. Dicionário`<TKey, TValue>` Dicionário
Dictionary<TKey, TValue> armazena pares chave-valor, permitindo uma consulta rápida de valores por chave com tempo de consulta próximo de O(1).
▶ Exemplo
using System;
using System.Collections.Generic;
Dictionary`<string, int>` scores = new Dictionary`<string, int>`
{
{ "Alice", 90 },
{ "Bob", 85 }
};
scores.Add("Charlie", 78);
scores["Bob"] = 92;
bool exists = scores.ContainsKey("Alice");
if (scores.TryGetValue("Dave", out int daveScore))
{
Console.WriteLine($"Dave: {daveScore}");
}
else
{
Console.WriteLine("Dave not found");
}
scores.Remove("Charlie");
Console.WriteLine($"Count: {scores.Count}");
Console.WriteLine("---Keys---");
foreach (string key in scores.Keys)
{
Console.WriteLine(key);
}
Console.WriteLine("---Values---");
foreach (int val in scores.Values)
{
Console.WriteLine(val);
}
Console.WriteLine("---Pairs---");
foreach (KeyValuePair`<string, int>` kv in scores)
{
Console.WriteLine($"{kv.Key}: {kv.Value}");
}
Dave not found
Count: 2
---Keys---
Alice
Bob
---Values---
90
92
---Pairs---
Alice: 90
Bob: 92
4. Métodos genéricos e classes genéricas
Os genéricos não se limitam a coleções; métodos e classes também podem usar parâmetros de tipo, tornando os algoritmos aplicáveis a vários tipos.
▶ Exemplo
using System;
T Max```<T>```(T a, T b) where T : IComparable```<T>```
{
return a.CompareTo(b) >= 0 ? a : b;
}
Console.WriteLine(Max(3, 7));
Console.WriteLine(Max("apple", "banana"));
7
banana
Exemplo de classe genérica:
using System;
class Box```<T>```
{
private T _value;
public Box(T value)
{
_value = value;
}
public T Value => _value;
public override string ToString() => $"Box<{typeof(T).Name}>: {_value}";
}
Box````<int>```` intBox = new Box````<int>````(42);
Box````<string>```` strBox = new Box````<string>````("hello");
Console.WriteLine(intBox);
Console.WriteLine(strBox);
Box<Int32>: 42
Box<String>: hello
5. Restrições genéricas
Use a palavra-chave where para impor restrições aos parâmetros de tipo, limitando o conjunto de tipos que podem ser utilizados.
▶ Exemplo
using System;
class Repository```<T>``` where T : class, new()
{
public T Create()
{
T instance = new T();
Console.WriteLine($"Created: {typeof(T).Name}");
return instance;
}
}
class Player
{
public string Name { get; set; } = "Unknown";
}
Repository<Player> repo = new Repository<Player>();
Player p = repo.Create();
Console.WriteLine($"Name: {p.Name}");
Created: Player
Name: Unknown
Referência às restrições comuns:
| Restrição | Descrição |
|---|---|
where T : class |
T deve ser um tipo de referência |
where T : struct |
T deve ser um tipo de valor |
where T : new() |
T deve ter um construtor público sem parâmetros |
where T : IFoo |
T deve implementar a interface IFoo |
where T : Base |
T deve herdar da classe Base |
6. Fila<T> Fila
Uma fila é uma estrutura de dados do tipo “primeiro a entrar, primeiro a sair” (FIFO). Use Enqueue para adicionar e Dequeue para remover.
▶ Exemplo
using System;
using System.Collections.Generic;
Queue````<string>```` queue = new Queue````<string>````();
queue.Enqueue("first");
queue.Enqueue("second");
queue.Enqueue("third");
string front = queue.Peek();
Console.WriteLine($"Peek: {front}");
while (queue.Count > 0)
{
Console.WriteLine($"Dequeue: {queue.Dequeue()}");
}
Peek: first
Dequeue: first
Dequeue: second
Dequeue: third
7. Stack<T> Stack
Uma pilha é uma estrutura de dados do tipo “último a entrar, primeiro a sair” (LIFO). Use Push para adicionar e Pop para remover.
▶ Exemplo
using System;
using System.Collections.Generic;
Stack````<int>```` stack = new Stack````<int>````();
stack.Push(10);
stack.Push(20);
stack.Push(30);
int top = stack.Peek();
Console.WriteLine($"Peek: {top}");
while (stack.Count > 0)
{
Console.WriteLine($"Pop: {stack.Pop()}");
}
Peek: 30
Pop: 30
Pop: 20
Pop: 10
8. Inicializadores de coleção
Os inicializadores de coleção permitem preencher elementos diretamente ao criar uma coleção, com uma sintaxe concisa e intuitiva.
▶ Exemplo
using System;
using System.Collections.Generic;
List````<int>```` nums = new List````<int>```` { 1, 2, 3, 4, 5 };
Dictionary`<string, double>` prices = new Dictionary`<string, double>`
{
{ "coffee", 3.5 },
{ "tea", 2.8 },
{ "juice", 4.0 }
};
Console.WriteLine(string.Join(", ", nums));
foreach (var kv in prices)
{
Console.WriteLine($"{kv.Key}: ${kv.Value}");
}
1, 2, 3, 4, 5
coffee: $3.5
tea: $2.8
juice: $4
9. Introdução a IEnumerable e iteradores
IEnumerable<T> é a interface básica para todas as coleções iteráveis, e o loop foreach depende dela. Usando yield return, você pode escrever facilmente iteradores personalizados (detalhes na Lição 35).
▶ Exemplo
using System;
using System.Collections.Generic;
IEnumerable````<int>```` GetRange(int start, int end)
{
for (int i = start; i <= end; i++)
{
yield return i;
}
}
foreach (int n in GetRange(1, 5))
{
Console.WriteLine(n);
}
1
2
3
4
5
yield return transforma um método em um iterador: cada chamada retorna um valor e faz uma pausa, e a próxima iteração retoma a partir do ponto em que foi interrompida. Para uma explicação completa, consulte a Lição 35.
❓ Perguntas Frequentes
P: Qual é a diferença entre List
<T>e as matrizes? R: List<T>permite adicionar e remover elementos dinamicamente, enquanto as matrizes têm comprimento fixo. A List é internamente baseada em uma matriz; quando é necessário redimensioná-la, ela cria automaticamente uma matriz maior e copia os elementos.
P: O que acontece se você adicionar uma chave duplicada a um Dictionary? R: Usar o método Add com uma chave duplicada gera uma exceção ArgumentException; usar a atribuição por indexador
dict[key] = valuesobrescreve o valor existente.
P: É possível usar
where T : classewhere T : structjuntos? R: Não, a restrição de classe exige um tipo de referência e a restrição de estrutura exige um tipo de valor — elas são mutuamente exclusivas.
P: É possível percorrer uma fila (Queue) e uma pilha (Stack) com o foreach? R: Sim, a percorrida com o foreach não remove elementos; ela apenas os lê. Os métodos Dequeue e Pop são os que realmente removem os elementos.
P: Por que a restrição genérica
where T : new()é necessária? R: Ela garante que você possa usarnew T()para criar uma instância dentro de um método. Sem essa restrição, o compilador não permite o uso do operadornewemT.
📖 Resumo
- Os genéricos proporcionam segurança de tipos e reutilização de código, evitando a sobrecarga do boxing e do unboxing
List<T>é uma matriz dinâmica que suporta diversas operações, como adicionar, remover, pesquisar, modificar, ordenar e localizarDictionary<TKey, TValue>armazena pares chave-valor, oferecendo desempenho de consulta O(1)- Métodos e classes genéricos tornam os algoritmos e as estruturas de dados aplicáveis a vários tipos
- As restrições
wherelimitam o intervalo dos parâmetros de tipo: classe, estrutura, new(), interfaces e classes base Queue<T>segue o princípio “primeiro a entrar, primeiro a sair”,Stack<T>segue o princípio “último a entrar, primeiro a sair”- Os inicializadores de coleções simplificam a criação e o preenchimento de coleções
IEnumerable<T>é a base para a percorrida, eyield returnpermite criar iteradores
📝 Exercícios
- Crie um
List<double>, adicione 5 notas, useFindAllpara filtrar as notas acima de 80 e exiba-as - Criar um
Dictionary<string, string>que armazene três mapeamentos entre capitais e províncias; implementar a consulta de uma província a partir do nome da cidade (usandoTryGetValue) - Escreva um método genérico `T Clamp
<T>(T value, T min, T max) where T : IComparable<T>que limite um valor dentro do intervalo [min, max] - Use
Queue<string>para simular uma fila de impressão: insira 3 nomes de documentos na fila, depois retire-os da fila e imprima-os em ordem - Escreva um método iterador
IEnumerable<int>utilizandoyield returnpara gerar os primeiros 10 termos da sequência de Fibonacci