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

CSHARP
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);
}
▶ Experimente
TEXT 📖 Somente leitura
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:

CSHARP
using System;
using System.Collections.Generic;

List````<int>```` numbers = new List````<int>````();
numbers.Add(42);

foreach (int n in numbers)
{
    Console.WriteLine(n);
}
TEXT 📖 Somente leitura
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

CSHARP
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)}");
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP
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}");
TEXT 📖 Somente leitura
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

CSHARP
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}");
}
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP
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"));
▶ Experimente
TEXT 📖 Somente leitura
7
banana

Exemplo de classe genérica:

CSHARP
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);
TEXT 📖 Somente leitura
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

CSHARP
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}");
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP
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()}");
}
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP
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()}");
}
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP
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}");
}
▶ Experimente
TEXT 📖 Somente leitura
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

CSHARP
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);
}
▶ Experimente
TEXT 📖 Somente leitura
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] = value sobrescreve o valor existente.

P: É possível usar where T : class e where T : struct juntos? 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 usar new T() para criar uma instância dentro de um método. Sem essa restrição, o compilador não permite o uso do operador new em T.

📖 Resumo

📝 Exercícios

  1. Crie um List<double>, adicione 5 notas, use FindAll para filtrar as notas acima de 80 e exiba-as
  2. 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 (usando TryGetValue)
  3. 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]
  4. 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
  5. Escreva um método iterador IEnumerable<int> utilizando yield return para gerar os primeiros 10 termos da sequência de Fibonacci
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%