C# - Tratando com Coleções (revisão)


Hoje eu vou escrever um pouco sobre coleções, e para que você entenda o assunto tratado aqui, você tem que conhecer os conceitos básicos sobre Arrays.

Arrays são um tipo de dados da plataforma .NET onde podemos tratar com diversos elementos de um mesmo tipo. A definição formal seria: "Arrays - Tipo de variável que armazena um conjunto de valores do mesmo tipo ; Um arranjo de elementos em uma ou mais dimensões ; Uma coleção de elementos do mesmo tipo no qual a posição de cada elemento está definida de forma única por um índice inteiro."

Para mais detalhes veja as referências sobre o assunto que eu já publiquei.

Vamos então ao assunto do artigo: Coleções ou Collections.

Dados estreitamente relacionadas podem ser tratados de forma mais eficiente quando agrupadas em uma coleção. Em vez de escrever código separado para lidar com cada objeto individual, você pode usar o mesmo código para processar todos os elementos de uma coleção.

Para gerenciar uma coleção, a classe Array e as classes do namespace System.Collections são usadas para adicionar, remover e modificar qualquer um dos elementos individuais de uma coleção ou um intervalo de elementos. Uma coleção inteira pode até mesmo ser copiados para outra coleção.

Algumas classes Collections têm as capacidades de classificação, e a maioria são indexadas. A Gestão de memória é feita automaticamente, e a capacidade de uma coleção é expandida, conforme necessário.

O que é uma coleção e qual a diferença entre uma coleção e um Array ?

As coleções são estruturas de dados enumeráveis que podem ser acessadas usando índices e chaves.

Uma coleção também armazena um conjunto de valores da mesma forma que um array a diferença é que uma coleção armazena os elementos como Object. Portanto as coleções tem capacidade de colecionar itens do tipo Object.

As classes de coleções podem ser encontradas no namespace System.Collections e elas dão suporte a pilhas, filas, listas e hash tables.(stacks, queues, lists , hash tables)

A maioria das coleções implementam as mesmas interfaces e estas interfaces podem ser herdadas para criar classes de coleções mais especializadas.

Como funciona o armazenamento de elementos em uma coleção ?

Uma coleção armazena elementos como Object, assim se eu guardar um item do tipo String em uma coleção ela irá armazenar um object. Se for um integer a mesma coisa e assim por diante.

Com isto temos sempre que estar tipando manualmente cada item da coleção quando formos tratá-los. Desta forma quando eu coloco um item em uma coleção faço o boxing e quando eu retiro um item da coleção faço o unboxing.

Obs: Boxing: É a conversão de um Value Type para um Reference Type e Unboxing: É quando um Reference Type (object) volta a ser um Value Type.

Dessa forma podemos enumerar algumas características das coleções:

Agora vamos definir alguns detalhes sobre as classes que podemos usar para tratar com coleções.

O namespace System.Collections fornece uma série de classes, métodos e propriedades para interagir com os estrutura de dados variáveis. As interfaces definidas neste espaço incluem:

A seguir, temos as classes que são derivadas da interface ICollection :

A interface IDictionary representa coleções que têm pares de valores e nome. As coleções quem herdam a interface IDictionary incluem:

A interface IList representa coleções que têm apenas valor. A seguir, temos as classes que herdam desta interface:

Quantas classes e interfaces !!!!

Não se assuste esse arsenal mostra que temos um grande leque de opções para o tratamento dos mais diversos tipos de coleções. No momento certo você poderá decidir qual delas usar aproveitando seus recursos.

Vamos falar um pouco sobre a interface IEnumerable.

A interface IEnumerable

A interface IEnumerable expõe o enumerador, que oferece suporte a uma iteração simples em uma coleção.

Um enumerador é um objeto que fornece um cursor somente leitura e para frente para um conjunto de itens.

A interface IEnumerable possui um método chamado GetEnumerator() que retorna um objeto que implementa a interface IEnumerator.

Obs: A interface IEnumerator possui os seguintes membros : Propriedade=Current, Métodos: MoveNext e Reset;

O trecho de código abaixo ilustra como um enumerador pode ser usado para iterar através de uma lista ou coleção de itens.

A seguir vemos um trecho de código que ilustra a utilização do método GetEnumerator():

using System;
using System.Collections;

namespace CSharp_Colecoes
{
    class Program
    {
        static void Main(string[] args)
        {
            String[] nomes = new String[2] { "Macoratti", "http://www.macoratti.net" };
            for (IEnumerator e = nomes.GetEnumerator(); e.MoveNext(); Console.WriteLine(e.Current)) ;

            Console.ReadKey();
        }
    }
}

Vamos entender o código acima:

- Definimos um array de strings com dois elementos e usamos um laço for para percorrer o array;
- O método GetEnumerator() retorna um objeto enumerador cada vez que é chamado;
- A condição avalia se o MoveNext() retorna um valor true , e MoveNext() retorna verdadeiro enquanto há itens na coleção;
- A propriedade Current retorna o objeto atual onde é automaticamente feito um implicitamente typecast para string através de uma chamada para o método ToString();
- A instrução Console.WriteLine() exibe os elementos conforme figura;

O método foreach poderia ser utilizado no exemplo, e, como ele chama o enumerador implicitamente, poderíamos reescrever o código conforme abaixo:

using System;
using System.Collections;

namespace CSharp_Colecoes
{
    class Program
    {
        static void Main(string[] args)
        {
            String[] nomes = new String[2] { "Macoratti", "http://www.macoratti.net" };

            foreach (string nome in nomes)
                Console.WriteLine(nome);

            Console.ReadKey();
        }
    }
}

Usando Coleções - ArrayList

Vamos agora mostrar na prática alguns dos recursos das coleções. Para isso eu vou usar a coleção ArrayList que herda da classe ArrayList.

A classe ArrayList é um array dinâmico de objetos heterogêneos.  Notou que a definição disse array dinâmico ???

Sim, em um ArrayList o tamanho é gerenciado de forma dinâmica automaticamente.

Enquanto que em um um array podemos armazenar apenas objetos do mesmo tipo, em um ArrayList podemos ter diferentes tipos de objetos, que são armazenados como tipos Object. Assim podemos ter um ArrayList que armazena float, integer, string, etc., todos armazenados como Object.

Um ArrayList utiliza índices para se referir a um objeto particular armazenado na coleção da mesma forma que um array. E o índice também é base zero, ou seja o primeiro elemento possui o índice zero.

Um ArrayList herda classe ArrayList que implementa a interface IList usando um array cujo tamanho é aumentando dinamicamente quando requerido.

A sintaxe básica para declarar um ArrayList é : ArrayList nomedoArrayList = new ArrayList();

No código a seguir estamos criando um ArrayList chamado bau, incluindo alguns elementos e usando algumas propriedades e métodos e ao final percorrendo e exibindo os elementos atuais do ArrayList usando um laço for:

using System;
using System.Collections;

namespace CSharp_Colecoes
{
    class Program
    {
        static void Main(string[] args)
        {
            ArrayList bau = new ArrayList();
            bau.Add("Macoratti");
            bau.Add("23/04/2012");
            bau.Add(2012);       //int
            bau.Add("xxxxx");   //string
            bau.Add(null);         //null
            bau.Add(true);        //boolean
            bau.Add("#####");   //string
            bau.Add(12.25);      //double
            bau.Add(14.45f);     //float

            Console.WriteLine("ArrayList bau");
            Console.WriteLine("capacidade = " + bau.Capacity);
            Console.WriteLine("tamanho    = " + bau.Count);
            Console.WriteLine("removendo o elemento xxxxx");
            bau.Remove("xxxxx");
            Console.WriteLine("inserindo um novo elemento : o fim");
            bau.Add("the end");

            Console.WriteLine("Percorrendo o ArrayList ");
            for (int indice = 0; indice < bau.Count; indice++)
            {
                Console.WriteLine("indice {0} - {1} ",  indice, bau[indice]);
            }
            Console.WriteLine("Limpando o bau ");
            
            bau.Clear();
            if (bau.Count == 0)
            {
                Console.WriteLine(" O bau esta vazio ....");
            }
            Console.ReadKey();
        }
    }
}

Note que pudemos incluir um novo elemento na coleção usando o método Add()
aumentando o seu tamanho sem ter que redimensionar a coleção via código;

Observe que a capacidade inicial do ArrayList é 16 o qual é aumentada após
a inclusão do elemento 17.

Este processo repetitivo de alocação de memória e cópia dos elementos pode ter um
custo de desempenho em algumas situações. Dessa forma é recomendado nestes casos
que a capacidade inicial da coleção seja definida usando a propriedade Capacity.
Ex: bau.Capacity = 10;

Usando Coleções - HashTable

A classe Hashtable é uma coleção onde você armazena objetos relacionados com uma chave de identificação. Para cada objeto que você incluir na hashtable você precisa informar um chave que identifica o objeto de forma única. Temos então que armazenar um par de valores : chave,objeto. Abaixo temos um exemplo de como usar armazenar objetos cliente em um Hashtable:

using System;
using System.Collections;

namespace HashTable
{
    class Program
    {
        static void Main(string[] args)
        {
            Hashtable hashTable = new Hashtable();
            
            hashTable.Add(1, "Macoratti");
            hashTable.Add(2, "http://www.macoratti.net");
            hashTable.Add(3, "Visual Basic");
            hashTable.Add(4, "ASP .NET");
            
            Console.WriteLine("As chaves da hastable :--");

            foreach (int k in hashTable.Keys)
            {
                Console.WriteLine("Chave {0}  {1}", k, hashTable[k]);
            }

            Console.WriteLine("Informe a chave para procurar : ");
            int p = int.Parse(Console.ReadLine());
            Console.WriteLine(hashTable[3].ToString());

            Console.ReadKey();
        }
    }
}

O nome hashtable e conseqüência do hash (um valor numérico) que é gerado quando você inclui uma chave para identificar o objeto de forma única. O que fica armazenado na coleção não é a chave mas o hash calculado.

Para recuperar o objeto você informa a sua chave.Para remover um item da classe Hashtable, usamos o método remove(). A declaração hashTable.Remove(3) ira remover o item "Visual Basic" do objeto Hashtable criado no código acima.

Usando Coleções - A classe Queue

Você pode comparar a classe Queue com as pessoas que estão esperando em uma fila.

A primeira pessoa que chega é a primeira a ser atendida,  toda pessoa que chega é atendida na ordem de chegada.

Este mecanismo é chamado FIFO : first in first out (primeiro a chegar primeiro a sair).

Esta classe permite tratar coleções de objetos fracamente tipadas que são ordenadas pela ordem no qual eles são incluídos na coleção. O código abaixo mostra como incluímos e acessamos objetos em uma fila:

using System;
using System.Collections;

namespace _Queue
{
    class Program
    {
        static void Main(string[] args)
        {
            Queue fila = new Queue();
            
            fila.Enqueue("Macoratti");
            fila.Enqueue("http://www.macoratti.net");
            fila.Enqueue("Visual Basic");

            Console.WriteLine("Tamanho da fila : " + fila.Count);
            
            while (fila.Count > 0)
            {
                Console.WriteLine(fila.Dequeue());
            }
            Console.ReadLine();
        }
    }
}

Observe que não usamos o método Add para incluir um objeto na fila mas sim o método Enqueue.

Para acessar um objeto na fila não usamos índice nem chave; simplesmente recuperamos o primeiro da fila e depois o próximo e assim por diante.

Usamos para isto o método Dequeue.

O método não só obtém o objeto cliente da fila mas também remove o objeto da coleção. Assim ao chamar o método novamente você pega o próximo objeto.

Você deve usar esta classe quando precisar tratar objetos que estão na ordem de sequência na coleção.

Usando Coleções - A classe Stack

A classe Stack funciona basicamente como a classe Queue com uma diferença :  O último objeto incluído na pilha é retornado primeiro . Imagine uma pilha de papéis , o primeiro que você pega da pilha foi o último que foi colocado nela. Este mecanismo é conhecido como LIFO : last in first out ( o último a entrar é o primeiro sair). Abaixo o código para preencher uma coleção Stack de objetos:

using System;
using System.Collections;

namespace _4_Stack
{
    class Program
    {
        static void Main(string[] args)
        {
            Stack pilha = new Stack();

            pilha.Push("Macoratti");
            pilha.Push("http://www.macoratti.net");
            pilha.Push("C-Sharp");

            Console.WriteLine("Tamanho da pilha " + pilha.Count);
            
            while (pilha.Count > 0)
                Console.WriteLine(pilha.Pop());

            Console.ReadLine();
        }
    }
}

O método Push é usado para incluir itens na pilha.

Para obter o último item incluído podemos usar o  método Pop ou o método Peek. A diferença é que o método Peek obtém o último objeto na pilha e o método Pop além de fazer isto remove o item da pilha.

Geralmente a utilização da classe Stack bem como da classe Queue fica restrita a uma possível implementação de um mecanismo LIFO ou FIFO mas é bom você saber que estas classe existem e como funcionam.

Usando Coleções - A classe SortedList

Podemos considerar a classe SortedList (Lista Ordenada) como uma combinação de um Array e de uma HashTable. Você pode acessar os itens em uma SortedList pelo índice (como um array) ou pela chave (como uma hashtable).

Como o nome já diz uma SortedList é ordenada. Esta ordenação é baseada em uma chave única para cada elemento na coleção. A sequência do índice é baseada na sequencia de ordenação. Devido a ordenação esta classe tende a ser mais lenta que a classe Hashtable.

No código abaixo temos como criar uma nova SortedList e incluir dois objetos clientes. O método Add é o mesmo usado para a classe HashTable de forma que você tem que atribuir uma chave para cada objeto que incluir.

using System;
using System.Collections;

namespace _5_SortedList
{
    class Program
    {
        static void Main(string[] args)
        {
            SortedList sortedList = new SortedList();
            sortedList.Add(1, "Macoratti");
            sortedList.Add(3, "http://www.macoratti.net");
            sortedList.Add(2, "C-Sharp");
            sortedList.Add(4, "Visual Basic");

            Console.WriteLine("Tamanho    : " + sortedList.Count);
            Console.WriteLine("Capacidade : " + sortedList.Capacity);
            Console.WriteLine("Exibindo os nomes : ");

            foreach (string str in sortedList.Values)
            {
                Console.WriteLine(str);
            }
            Console.ReadKey();
        }
    }
}

Coleções de tipos Seguros

As coleções de Tipo Seguros (Type Safe) são coleções que compreendem um tipo conhecido. Elas suportam a indexação como um array e tem uma série de benefícios. Uma coleção fortemente tipada é implementada usando qualquer uma das seguintes classes:

A seguir, temos algumas das vantagens do uso dessas coleções:

Como exemplo vamos ver a utilização da classe CollectionBase.

A classe CollectionBase

A classe abstrata CollectionBase (você sabe o que significa o termo 'classe abstrata' ? ) fornece a classe base para uma coleção customizada fortemente tipada . Um a implementação básica seria :

Nota: Uma classe abstrata não pode ser instanciada (não podemos chamar seus construtores) e é utilizada quando você deseja fornecer uma interface comum a diversos membros de uma hierarquia de classes. Os métodos declarados na classe abstrata serão implementados em suas subclasses usando polimorfismo.(Uma classe abstrata pode definir o protocolo para uma operação sem fornecer o código correspondente.)

O exemplo a seguir mostra como podemos implementar uma classe de coleção personalizada.

O código mostra como podemos projetar uma classe de coleção personalizada usando a sub-classificação da classe CollectionBase.

using System;
using System.Collections;

public class Produto : CollectionBase
{
  public Produto this[int index]
  {
    get
    {  return ((Produto)(List[index])); }
    set 
    {  List[index] = value;}
  }
 
  public bool Contains(Produto produto)
  {  return List.Contains(produto); }
 
  public int Add(Produto Produto)
  {  return List.Add(Produto);  }
   
  public void Remove(Produto produto)
  {  List.Remove(Produto);  }
}
End Class

No código acima a classe Produto herda da classe Pai CollectionBase . Incluímos um método Add para incluir Produtos e um também métodos para remover(Remove) e verificar se a coleção contém um produto(Contains).

Embora as coleções sejam um recurso poderoso, elas devem ser usadas com critério sendo que devemos sempre escolher o tipo adequado de coleção que devemos usar pois elas podem degradar o desempenho devido a sobrecarga na realização das operações boxing e unboxing.

O conselho é sempre procurar escolher a coleção mais simples possível que fornece a funcionalidade desejada para minimizar tais problemas.

Pegue o projeto completo aqui: CSharp_Colecoes.zip

Rom 8:9 Vós, porém, não estais na carne, mas no Espírito, se é que o Espírito de Deus habita em vós. Mas, se alguém não tem o Espírito de Cristo, esse tal não é dele.
Rom 8:10
Ora, se Cristo está em vós, o corpo, na verdade, está morto por causa do pecado, mas o espírito vive por causa da justiça.

Referências:


José Carlos Macoratti