A List: Uma Coleção que Cresce e Encolhe

A List: Uma Coleção que Cresce e Encolhe

A coleção que resolve a rigidez do array: a List, que cresce e encolhe conforme itens são adicionados ou removidos. A aula cobre criação, adição, remoção, acesso por índice, a contagem com Count e os métodos prontos para buscar, verificar presença e limpar a coleção.
Linguagem C#

• • 11 min de leitura

O array da aula anterior é uma estrutura fundamental, mas tem uma limitação que rapidamente incomoda: seu tamanho é fixo. Uma vez criado com cinco posições, ele terá para sempre cinco posições — você não pode adicionar um sexto elemento nem remover um existente para encurtá-lo. Na prática, porém, raramente sabemos de antemão quantos valores teremos. Uma lista de tarefas ganha e perde itens ao longo do dia; um carrinho de compras cresce a cada produto adicionado; os nomes de convidados que confirmam presença aumentam com o tempo. Para essas situações — que são a maioria —, o C# oferece uma estrutura mais flexível: a List (lista). Diferentemente do array, a List cresce e encolhe conforme a necessidade, permitindo adicionar e remover elementos livremente. É, de longe, a coleção que você mais usará no dia a dia, e esta aula a apresenta.

A List: um array que se adapta

Pense na List como um array elástico. Ela guarda uma sequência ordenada de valores do mesmo tipo, acessíveis por índice, exatamente como o array — mas, ao contrário dele, seu tamanho não é fixo: você pode acrescentar elementos ao fim, inserir no meio, ou remover, e a lista se ajusta automaticamente. Essa flexibilidade tem um pequeno custo interno (a List, por baixo, gerencia um array que ela mesma redimensiona quando necessário), mas esse custo é irrelevante para a imensa maioria dos usos, e a conveniência que ela oferece é enorme. A regra prática que você pode adotar desde já: quando o número de elementos pode variar, use List; use array apenas quando o tamanho é genuinamente fixo e conhecido. Na dúvida, a List é a escolha mais segura.

Criando uma List

A criação de uma List tem uma sintaxe um pouco diferente da do array, e vale explicá-la com cuidado, pois traz um elemento novo. Veja:

// Cria uma lista vazia que guardará textos:
List<string> tarefas = new List<string>();

O tipo é List<string> — leia-se "uma lista de strings". Os sinais de menor e maior com o tipo dentro (<string>) indicam o que a lista vai guardar; uma lista de números seria List<int>, uma de decimais List<decimal>. Essa notação com < > é a forma de dizer "uma lista de tal coisa", e você a verá em todas as coleções flexíveis do C#. A parte new List<string>() cria efetivamente a lista, começando vazia (a palavra new, como nos arrays, sinaliza a criação de algo novo). Existe também uma forma mais curta de escrever a mesma criação, muito comum no C# moderno, em que a repetição do tipo à direita é substituída por new():

List<string> tarefas = new();   // forma curta, equivalente à anterior

Você também pode criar uma lista já com valores iniciais, listando-os entre chaves, como fazíamos com arrays:

List<int> numeros = new() { 10, 20, 30 };   // lista já com três números

Adicionando e removendo: o coração da flexibilidade

Aqui está o que distingue a List do array. Para adicionar um elemento ao fim da lista, usamos o método Add (em inglês, "adicionar"):

List<string> tarefas = new();

tarefas.Add("Estudar C#");     // a lista agora tem 1 item
tarefas.Add("Fazer compras");  // agora tem 2 itens
tarefas.Add("Ligar para o médico");  // agora tem 3 itens

A cada Add, a lista cresce para acomodar o novo elemento — algo impossível num array. Note que Add é um método (as aulas anteriores!), acionado com o ponto a partir da lista, exatamente como Console.WriteLine é acionado a partir de Console. Para remover um elemento, há duas opções principais: Remove, que remove pela igualdade de valor (encontra e remove a primeira ocorrência daquele valor), e RemoveAt, que remove pela posição (índice):

tarefas.Remove("Fazer compras");   // remove o item cujo valor é "Fazer compras"
tarefas.RemoveAt(0);                // remove o item da posição 0 (o primeiro)

Após remoções, a lista encolhe, e os elementos restantes se reorganizam para preencher os espaços — não ficam "buracos". Essa capacidade de adicionar e remover à vontade é o que torna a List tão natural para modelar coleções do mundo real, que mudam com o tempo.

Acessando elementos e o tamanho

Em muitos aspectos, a List se comporta como o array que você já conhece. Você acessa elementos por índice, com colchetes, e a contagem começa em zero:

List<string> tarefas = new() { "Estudar", "Comprar", "Ligar" };

Console.WriteLine(tarefas[0]);   // Estudar — o primeiro, na posição 0
tarefas[1] = "Cozinhar";         // modifica o elemento da posição 1

Uma diferença de vocabulário: enquanto o array informa seu tamanho por Length, a List o informa por Count (em inglês, "contagem"). É o mesmo conceito — quantos elementos há —, apenas com outro nome:

Console.WriteLine(tarefas.Count);   // exibe o número de itens na lista

Assim como no array, os índices válidos vão de 0 a Count - 1, e tentar acessar uma posição inexistente quebra o programa com o mesmo tipo de erro (índice fora do intervalo). Toda a intuição que você construiu sobre índices no array vale para a List; a diferença essencial está no tamanho variável e nos métodos Add/Remove que o gerenciam.

Métodos úteis da List

Além de Add e Remove, a List oferece métodos convenientes que resolvem tarefas comuns sem que você precise escrever laços. Alguns dos mais úteis:

List<string> nomes = new() { "Ana", "Beto", "Carla" };

Console.WriteLine(nomes.Contains("Beto"));   // True — verifica se contém um valor
Console.WriteLine(nomes.IndexOf("Carla"));   // 2 — em que posição está um valor
nomes.Insert(1, "Bruno");                    // insere "Bruno" na posição 1
nomes.Clear();                               // esvazia a lista por completo

O Contains verifica se um valor está na lista, devolvendo verdadeiro ou falso — perfeito para uma condição de if. O IndexOf informa a posição de um valor (ou -1 se não estiver presente). O Insert adiciona um elemento numa posição específica, empurrando os demais para frente. O Clear remove todos os elementos de uma vez. Esses métodos poupam trabalho e tornam o código mais legível: em vez de escrever um laço para procurar um item, você chama Contains; em vez de um laço para esvaziar, chama Clear. Conhecer os métodos que uma coleção oferece é parte de dominá-la.

Um exemplo completo: uma lista de tarefas viva

Juntando tudo, veja uma pequena lista de tarefas que cresce, é consultada e encolhe — algo que um array não poderia fazer:

List<string> tarefas = new();

tarefas.Add("Estudar");
tarefas.Add("Cozinhar");
tarefas.Add("Descansar");
Console.WriteLine($"Tenho {tarefas.Count} tarefas.");   // Tenho 3 tarefas.

// Concluí "Cozinhar", então a removo:
tarefas.Remove("Cozinhar");
Console.WriteLine($"Agora tenho {tarefas.Count} tarefas.");   // Agora tenho 2 tarefas.

// Verifico se ainda preciso estudar:
if (tarefas.Contains("Estudar"))
    Console.WriteLine("Ainda preciso estudar!");

Este exemplo captura a essência da List: uma coleção que reflete uma realidade em mudança. Adicionamos tarefas conforme surgem, removemos as concluídas, consultamos seu estado — tudo com a coleção se ajustando naturalmente. É esse dinamismo que faz da List a ferramenta de escolha para a maioria das coleções que você modelará.

A rigidez do array ficou para trás. A List guarda uma sequência de valores do mesmo tipo sem exigir que o tamanho seja decidido de antemão: ela cresce ao receber um item e encolhe ao perder um, o que a torna a coleção adequada para a maioria absoluta das situações reais, em que a quantidade de dados só se conhece durante a execução.

Além de adicionar e remover, ela oferece operações prontas para verificar presença, localizar posição, contar e limpar — trabalho que, com um array, seria escrito à mão toda vez. Na prática, a List é a coleção que se usa por padrão, e o array fica reservado aos casos em que o tamanho é realmente fixo.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Crie uma List<string> vazia de nomes de cidades, adicione quatro cidades com Add, exiba quantas há com Count, e depois remova uma delas com Remove, exibindo a contagem novamente. Confirme que a lista cresceu e encolheu.

Ver resposta

✓ Resposta: Exemplo:

List<string> cidades = new();
cidades.Add("Recife");
cidades.Add("Olinda");
cidades.Add("Caruaru");
cidades.Add("Petrolina");
Console.WriteLine($"Cidades: {cidades.Count}");   // Cidades: 4
cidades.Remove("Olinda");
Console.WriteLine($"Cidades: {cidades.Count}");   // Cidades: 3

A contagem passou de 4 para 3, demonstrando que a lista cresceu com os Add e encolheu com o Remove — algo que um array de tamanho fixo não permitiria.

Exercício 2

Explique, com suas palavras, a principal diferença entre um array e uma List. Dê um exemplo de situação em que a List é claramente mais apropriada, e outro em que um array seria suficiente.

Ver resposta

✓ Resposta: A principal diferença é que o array tem tamanho fixo, definido na criação e imutável, enquanto a List tem tamanho variável, podendo crescer e encolher com Add e Remove. A List é claramente mais apropriada, por exemplo, para uma lista de tarefas ou um carrinho de compras, cujo número de itens muda ao longo do tempo. Um array seria suficiente, por exemplo, para guardar os sete dias da semana ou os doze meses do ano — conjuntos cujo tamanho é fixo e conhecido, que nunca precisarão crescer nem encolher.

Exercício 3

Crie uma List<int> com alguns números, e use os métodos Contains e IndexOf para verificar se um certo número está presente e, se estiver, em que posição. Explique o que cada método devolve.

Ver resposta

✓ Resposta: Exemplo:

List<int> numeros = new() { 10, 25, 40, 55 };
Console.WriteLine(numeros.Contains(40));   // True
Console.WriteLine(numeros.IndexOf(40));    // 2
Console.WriteLine(numeros.IndexOf(99));    // -1 (não está presente)

O Contains devolve um bool: verdadeiro se o valor está na lista, falso caso contrário. O IndexOf devolve a posição (índice) da primeira ocorrência do valor, ou -1 se o valor não estiver presente na lista.

Exercício 4

Crie uma List<string> com três nomes e experimente: acesse o elemento da posição 1 e exiba-o; modifique o elemento da posição 0; insira um novo nome na posição 1 com Insert; e exiba a lista inteira com um foreach. Descreva como a lista mudou a cada operação.

Ver resposta

✓ Resposta: Exemplo:

List<string> nomes = new() { "Ana", "Beto", "Carla" };
Console.WriteLine(nomes[1]);        // Beto (posição 1)
nomes[0] = "Alice";                 // posição 0 vira "Alice"
nomes.Insert(1, "Bruno");           // "Bruno" entra na posição 1
foreach (string nome in nomes)
    Console.WriteLine(nome);        // Alice, Bruno, Beto, Carla

Mudanças: exibiu "Beto" (posição 1); a posição 0 passou de "Ana" para "Alice"; o Insert colocou "Bruno" na posição 1, empurrando "Beto" e "Carla" uma posição para frente; a lista final tem quatro nomes.

Exercício 5

Sem código: a List usa Count para informar seu tamanho, enquanto o array usa Length. Além dessa diferença de nome, explique por que a List precisa de métodos como Add e Remove que o array não tem. O que a existência desses métodos revela sobre a natureza da List?

Ver resposta

✓ Resposta: A List precisa de Add e Remove porque seu tamanho é variável — ela é projetada justamente para crescer e encolher —, e esses métodos são as ferramentas que realizam essas mudanças, acrescentando ou retirando elementos e ajustando o tamanho da coleção. O array não tem esses métodos porque seu tamanho é fixo: uma vez criado, não há como adicionar ou remover posições, então não faria sentido oferecer operações que alterem a quantidade de elementos. A existência de Add e Remove revela que a List é, por natureza, uma coleção dinâmica, feita para representar conjuntos que mudam ao longo do tempo, ao contrário do array, que representa um conjunto de tamanho definitivo.

Comentários

Mais em Linguagem C#

Validação e Robustez em APIs
Validação e Robustez em APIs

O princípio inegociável de qualquer API: nunca confie na entrada. A aula cobre…

Laços de Repetição: while e for
Laços de Repetição: while e for

Os dois laços fundamentais do C#: o while, que repete enquanto uma condição…

MongoDB e o Mundo NoSQL em C#
MongoDB e o Mundo NoSQL em C#

A outra filosofia de organizar dados: bancos NoSQL orientados a documentos. A…