C# - Trabalhando com coleções (revisitado)


 Vamos repassar neste artigo os principais conceitos e usos das coleções na linguagem C#.

As coleções são classes especializadas para armazenamento e obtenção de dados muito utilizaas na plataforma .NET.

Se você precisar armazenar vários valores em uma variável, então você pode considerar usar uma coleção.  Uma coleção é uma estrutura de dados na memória que pode gerenciar vários itens de maneiras diferentes, podendo possuir algumas funcionalidades compartilhadas.

Os tipos mais comuns no .NET Standard 2.0 para trabalhar com coleções são mostrados na tabela a seguir:

Namespace Tipos Descrição
System.Collections IEnumerable, IEnumerable<T> Interfaces e classes base usadas pela coleções
System.Collections.Generics List<T>, Dictionary<T>
Queue<T>,  Stack<T>
Estas coleções permitem especificar o tipo que você deseja armazenar usando um parâmetro do tipo genérico
System.Collections.Concurrent BlockingCollection, ConcurrentQueue,
ConcurrentDictionary
Estas coleções são seguras para usar em cenários de
multithread
System.Collections.Immutable ImmutableArray, ImutableList,
ImmutableDictionary, ImmutableQueue
Coleções projetadas para cenários onde o conteúdo da
coleção nunca deve mudar

Características comuns a todas as coleções

Todas as coleções implementam a interface ICollection; isso significa que elas devem ter uma propriedade Count para contar quantos itens existem.

Por exemplo, se tivéssemos uma coleção chamada Passageiros, poderíamos fazer o seguinte:

int quantidade = Passageiros.Count;

Todas as coleções implementam a interface IEnumerable, o que significa que devem ter um método GetEnumerator que retorna um objeto que implementa IEnumerator; isso significa que elas devem ter um método MoveNext e uma propriedade Value para poder iterar sobre os itens usando a instrução foreach.

Por exemplo, para realizar uma ação em todos os itens da coleção dos Passageiros, podemos fazer o seguinte:

foreach (var passageiro em Passageiros)
{
    
// faça algo com cada passageiro
}


Para entender melhor as coleções, podemos analisar as interfaces mais comuns que elas implementam:


Pela figura vemos que :

As listas, ou seja, um tipo que implementa IList, são coleções ordenadas, o que significa que implementam ICollection; então elas devem ter :

  1. Uma propriedade Count;
  2. Um método Add para colocar um item no final da coleção;
  3. Um método Insert para colocar um item na lista em uma posição especificada;
  4. E um método RemoveAt para remover um item em uma posição especificada;

Temos assim diferentes tipos de coleções :  Listas, dicionários, pilhas(stacks), filas (queues), conjuntos (sets) e outras coleções especializadas. 

Vejamos um resumo das principais coleções.

Listas (List)

As listas são uma boa opção quando você deseja controlar manualmente a ordem dos itens em uma coleção. Cada item em
uma lista possui um índice (ou posição) exclusivo que é atribuído automaticamente.

Os itens podem ser de qualquer tipo (eles tem que ser todos do mesmo tipo) e os itens podem ser duplicados. Os índices são tipos inteiros e começam em 0, então o o primeiro item em uma lista está no índice 0.

Se um novo item (por exemplo, New York) for inserido entre Rio e Sydney, o índice de Sydney será incrementado automaticamente.

Portanto, você deve estar ciente de que o índice de um item pode mudar após a inserção ou remoção de itens, conforme mostrado na  a seguir:

Exemplo:

using System.Collections.Generic;
using static System.Console;

var cidades = new List<string>();
cidades.Add("Londres");
cidades.Add("Paris");
cidades.Add("Roma");

WriteLine("Lista Inicial");
foreach (string cidade in cidades)
{
    WriteLine($" {cidade}");
}

WriteLine($"Primeira cidade : {cidades[0]}.");
WriteLine($"Última cidade : {cidades[cidades.Count - 1]}.");
cidades.Insert(0, "São Paulo");
WriteLine("Depois de incluir São Paulo no índice 0");

foreach (string cidade in cidades)
{
    WriteLine($" {cidade}");
}

cidades.RemoveAt(1);
cidades.Remove("Roma");

WriteLine("Após remover duas cidades");

foreach (string cidade in cidades)
{
    WriteLine($" {cidade}");
}

ReadLine();

Resultado:

Dicionários(Dictionary)

Os dicionários são uma boa escolha quando cada valor (ou item) tem um subvalor único que pode ser usado como uma chave para localizar rapidamente o valor na coleção. (A chave deve ser única)

Se você esta armazenando uma lista de pessoas, você pode usar o número da identidade como a chave. Pense na chave como uma entrada de índice em um dicionário do mundo real. Ela permite que você encontre rapidamente a definição de uma palavra porque as palavras (por exemplo, chaves) são mantidas classificadas.

Tanto a chave quanto o valor podem ser de qualquer tipo. Este exemplo usa strings para ambas:

Exemplo:

using System.Collections.Generic;
using static System.Console;

var keywords = new Dictionary<string, string>();

keywords.Add("int", "tipo dados inteiro de 32-bit");
keywords.Add("long", "tipo dados inteiro 64-bit");
keywords.Add("float", "Numero de ponto flutuante de simples precisão");

WriteLine("Keywords e suas definições");

foreach (KeyValuePair<string, string> item in keywords)
{
    WriteLine($" {item.Key}: {item.Value}");
}

WriteLine($"A definição de long é : {keywords["long"]}");

Resultado:

Pilhas (Stacks)

As pilhas são uma boa escolha quando você deseja implementar o comportamento último a entrar (Last In), primeiro a sair(FirstOut) (LIFO).

Com uma pilha, você só pode acessar diretamente um item no topo da pilha, embora possa enumerar para ler através de toda a pilha de itens.  Você não pode, por exemplo, acessar o segundo item de uma pilha.

Por exemplo, os processadores de texto usam uma pilha para lembrar a sequência de ações que você fez recentemente, e então quando você pressiona Ctrl+Z, isso irá desfazer a última ação na pilha, e então a próxima ação e assim por diante.

Exemplo:

using static System.Console;
using System.Collections;

Stack planetasRochosos = new Stack();

planetasRochosos.Push("Mercúrio");
planetasRochosos.Push("Vênus");
planetasRochosos.Push("Terra");
planetasRochosos.Push("Marte");

foreach (string item in planetasRochosos)
    Write("{0}\t", item);

ReadLine();

Filas (Queues)

As filas são uma boa opção quando você deseja implementar o comportamento primeiro a entrar (First In), primeiro a sair (FirstOut) (FIFO).

Com uma fila, você só pode acessar diretamente um item na frente da fila, embora possa enumerar para ler toda a fila de itens. Você não pode, por exemplo, acessar o segundo item de uma fila.

Por exemplo, processos em segundo plano usam uma fila para processar itens de trabalho na ordem em que chegam,  igual
a pessoas na fila do correio.   

Exemplo:

using static System.Console;
using System.Collections;

Queue planetasRochosos = new Queue();

planetasRochosos.Enqueue("Mercúrio");
planetasRochosos.Enqueue("Vênus");
planetasRochosos.Enqueue("Terra");
planetasRochosos.Enqueue("Marte");

foreach (string item in planetasRochosos)
    Write("{0}\t", item);

ReadLine();

Conjuntos (HashSets)

Conjuntos são uma boa escolha quando você deseja realizar operações de conjunto entre duas coleções. Por exemplo, você pode ter duas coleções de nomes de cidades e deseja saber quais nomes aparecem em ambos os conjuntos (conhecido como a interseção entre os conjuntos).

Características da classe HashSet

- A classe HashSet<T> fornece operações de conjunto de alto desempenho. Um conjunto é uma coleção que não contém elementos duplicados e cujos elementos não estão em uma ordem específica.
- A capacidade de um objeto HashSet<T> é o número de elementos que o objeto pode conter;
- A capacidade de um objeto HashSet<T> aumenta automaticamente conforme os elementos são adicionados ao objeto;
- Uma coleção HashSet<T> não é classificada e não pode conter elementos duplicados;
- A classe HashSet<T> fornece muitas operações matemáticas de conjuntos, como adição de conjuntos (uniões) e subtração de conjuntos.

Exemplo:

using System;
using System.Collections.Generic;
HashSet<string> nomes = new HashSet<string> {"Macoratti","Miriam","Janice"};
//valores duplicados não são incluidos
nomes.Add("Macoratti");
foreach (var name in nomes)
{
    Console.WriteLine(name);
}
Console.ReadKey();

Recapitulamos assim, de forma resumida, alguns dos conceitos básicos das principais coleções existentes na plataforma .NET.

E estamos conversados.

"E eu, quando o vi, caí a seus pés como morto; e ele pôs sobre mim a sua destra, dizendo-me: Não temas; Eu sou o primeiro e o último; E o que vivo e fui morto, mas eis aqui estou vivo para todo o sempre. Amém. E tenho as chaves da morte e do inferno."
Apocalipse 1:17,18

Referências:


José Carlos Macoratti