Desenvolvimento - Visual Basic .NET

Usando coleções com Visual Basic .NET

Todas as coleções existentes no Microsoft .NET Framework derivam, direta ou indiretamente da interface ICollection, sob o namespace System.Collections, compartilhando muitas funcionalidades mais os tradicionais métodos para adicionar, remover e buscar elementos...

por Wallace Cézar Sales dos Santos



Introdução

Uma coleção é um conjunto de objetos similarmente tipados que são agrupados num único objeto.

Todas as coleções existentes no Microsoft .NET Framework derivam, direta ou indiretamente da interface ICollection, sob o namespace System.Collections, compartilhando muitas funcionalidades mais os tradicionais métodos para adicionar, remover e buscar elementos.

Algumas dessas funcionalidades incluem enumeradores, que são objetos que permitem e realizam repetições através de uma única coleção associada, membros de sincronização, que fornecem segurança no thread quando acessando elementos da coleção e métodos para cópias, através do uso do método CopyTo, que permite que as coleções possam ser copiadas para um array.

As coleções podem geralmente serem divididas em três categorias:

  • Coleções genéricas – são as variações mais comuns de coleções de dados, como hash tables, filas, pilha, dicionários e listas;
  • Coleções de Bits – são as coleções onde os elementos são bits sinalizadores. Elas comportam-se levemente diferente das outras coleções;
  • Coleções especializadas – são as coleções construídas com propósitos altamente definidos, usualmente para manipular tipos de elementos específicos, como a StringDictionary. Coleções podem variar, dependendo de como os elementos são armazenados, como são classificados, como as buscas são realizadas e como as comparações são realizadas.
Coleções genéricas

Como já exposto, coleções genéricas são são as variações mais comuns de coleções de dados, como hash tables, filas, pilha, dicionários e listas. Essas coleções são baseadas nas interfaces ICollection , IList ou IDictionary. As interfaces IList e IDictionary implementam a interface ICollection. As coleções baseadas na interfaces ICollection e IList possuem elementos que contém somente um valor (Array, ArrayList, Queue ou Stack). As coleções baseadas na interface IDictionary implementam elementos que contém valor e chave (Hashtable, SortedList).

As classes System.Collection.Queue e System.Collection.Stack são ideais para armazenamento temporário de informação, ou seja quando desejamos descartar o elemento após retirar seu valor. A diferença entre as duas reside no fato da primeira criar uma coleção onde o primeiro elemento a entrar é o primeiro a sair da coleção e na segunda, o oposto.

Shared Sub ExecQueue()
Dim q As New System.Collections.Queue
q.Enqueue("Desenvolvendo")
q.Enqueue(" com ")
q.Enqueue("VB.NET")
Console.WriteLine("Número de elementos: {0}", q.Count)
Console.WriteLine("Elementos existentes:")
Dim i As Int32
For i = 0 To q.Count - 1
Console.Write(q.Dequeue())
Next
End Sub

Listagem 1 – Acessando a classe System.Collection.Queue

A classe Queue (acessada na listagem 1), assim como a classe Stack, não possue uma propriedade Item. Na prática, isso quer dizer que não podemos acessar seus elementos pelo seu índice. A classe Queue nos fornece o método Enqueue com a finalidade de adicionarmos um tipo Object à coleção e o método Dequeue com a finalidade de retornar e retirar um elemento da coleção. Observe que para adicionarmos, passamos como argumento um objeto, porém para retirar não informamos qual desejamos: o primeiro a entrar é o primeiro a sair! Para podermos verificar um elemento sem retirá-lo da coleção, ambas as classes nos fornecem o método Peek para essa finalidade.

A classe System.Collection.Hashtable (acessada na listagem 2) é uma classe em que cada elemento da coleção é representado por um par de chave e valor. Uma Hashtable consiste num depósito que contém os elementos da coleção, funcionando como um subgrupo virtual e que realiza a busca e retirada dos elementos de forma mais rápida e fácil que a maioria das coleções. Isto é possível porque cada depósito é associado com um código hash baseado no valor do elemento.

Shared Sub ExecHashtable()
Dim h As New System.Collections.Hashtable(3)
h.Add(1, "Desenvolvendo")
h.Add(2, " com ")
h.Add(3, "VB.NET")
Console.WriteLine("Trabalhando com a coleção Hashtable")
Console.WriteLine("Número de elementos: {0}", h.Count)
Console.WriteLine("Apresentando as informações: ")
Dim i As Int32
For i = 1 To h.Count
Console.WriteLine("Chave-> {0} : Valor-> {1}", _
i.ToString(), h.Item(i))
Next
Console.WriteLine("Removendo o item com a chave 2, utilizando o método Remove:")
h.Remove(2)
Console.WriteLine("{0} {1}", h.Item(1), h.Item(3))
End Sub

Listagem 2 – Acessando a classe System.Collection.Hashtable

A primeira grande diferença que notamos é o fato de podermos realizar acesso aleatório aos elementos da coleção, através da propriedade item e o valor da chave (Key) permitindo-nos o acesso rápido ao valor contido na coleção. Podemos também, através do método Remove, retirar qualquer elemento da coleção, informando o valor de sua propriedade Key. Um outro ponto muito importante do uso da coleção Hashtable está no momento em que executamos o construtor seu construtor – Dim h As New System.Collections.Hashtable(3) – onde informamos o número inteiro 3, que define a capacidade da nossa coleção, único momento em que podemos realizar essa tarefa e que melhora sensivelmente a performance de de nossa coleção, pois elimina a necessidade de redimensionamento da coleção a medida que são adicionados novos elementos.

A classe System.Collection.ArrayList (acessada na listagem 3) é o que podemos chamar de evolução do Array. Possue funcionalidades típicas de coleções e que não são oferecidas pelo Array:

  • Não possue capacidade fixa como um Array, sendo automaticamente expandida quando necessário;
  • Fornece métodos que adiciona, insere ou remove vários elementos de uma vez. O Array somente é possível adicionar ou remover um elemento por vez;
  • Uma versão sincronizada é fácil de criar utilizando o método Synchronized. Com array não é tão simples;
  • Fornece métodos que retornam conectores “read-only” e “fixed-size” para uma coleção. Array não realiza essa operação.
Porém, deixa também a desejar em relação ao Array em relação a alguns aspectos:
  • Você não pode definir o limite inferior da coleção. Ela sempre se iniciará com zero, diferente do Array que é possível definir outro valor;
  • Possue somente uma dimensão. Um Array pode ser multidimensional;
  • Possui performance pior que um Array, se este for de um tipo específico. Isto ocorre porque os elementos do ArrayList são do tipo Object e, assim sendo, são necessárias operações de “boxing” e “unboxing” para armazenar e retirar os elementos da coleção, consumindo mais recursos do sistema.
Shared Sub ExecArraylist()
Dim al As New System.Collections.ArrayList(3)
al.Add("Desenvolvendo")
al.Add(" com ")
al.Add("VB.NET")
Console.WriteLine("Trabalhando com a coleção ArrayList")
Console.WriteLine("Número de elementos: {0}", al.Count)
Console.WriteLine("Apresentando as informações: ")
Dim i As Int32
For i = 0 To al.Count - 1
Console.Write("{0}", al.Item(i))
Next
Console.WriteLine("Removendo o item com índice 1, utilizando o método RemoveAt:")
al.RemoveAt(1)
For i = 0 To al.Count - 1
Console.Write("{0} ", al.Item(i))
Next
End Sub

Listagem 3 – Acessando a classe System.Collection.ArrayList

Da mesma forma que vimos na classe Hashtable, podemos também realizar acesso aleatório aos elementos da coleção.


Coleções de Bits

Coleções de Bits são aquelas onde os elementos são bits sinalizadores. Isto ocorre porque cada elemento é um bit e não um objeto e consequentemente, tem um comportamento levemente diferente das demais coleções.

A classe System.Collections.BitArray é uma classe que gerencia um compacto array de bits, os quais são representados como booleans, onde o valor true indica indica que o bit é “1” e false indica que o bit é “0”. Sua capacidade é sempre igual ao valor da sua propriedade Count. Se elementos são adicionados, a propriedade Lengh é incrementada, se são retirados, ela é decrementada. Como trata-se de uma coleção que manipula bits, ela possue métodos que não são encontrados em outras coleções, métodos esses próprios para a manipulação de bits: And, Or, Xor, Not e SetAll. Esse métodos permitem que múltiplos elementos sejam modificados uma única vez.

Shared Sub ExecBitArray()
Dim ba As New System.Collections.BitArray(3)
Console.WriteLine("Propriedade Length: {0}; Propriedade Count: {1}", ba.Length, ba.Count)
Dim i As Int32
For i = 0 To ba.Count - 1
Console.WriteLine("Valor do Bit {0}: {1}", i, ba.Item(i))
Next
Console.WriteLine("Alterando os valores para true, através do método SetAll:")
ba.SetAll(True)
For i = 0 To ba.Count - 1
Console.WriteLine("Valor do Bit {0}: {1}", i, ba.Item(i))
Next
Console.WriteLine("Alterando o valor do segundo Bit para False:")
ba.Item(1) = False
For i = 0 To ba.Count - 1
Console.WriteLine("Valor do Bit {0}: {1}", i, ba.Item(i))
Next
Console.WriteLine("Criando um novo BitArray e definindo os elementos como um array de inteiros:")
Dim mArray As Int32() = New Int32(1) {1, 2}
Dim ba1 As New System.Collections.BitArray(mArray)
For i = 0 To ba1.Count - 1
Console.WriteLine("Valor do Bit {0}: {1}", i, ba1.Item(i))
Next
End Sub

Listagem 4 – Acessando a classe System.Collection.BitArray

Observando a listagem 4, verificamos que inicializamos o objeto “ba” informando o número de bits iniciais: “3”. O construtor da classe BitArray possue várias sobrecargas, permitindo-nos inicializar informando quantos bits estarão sendo armazenados na coleção e, neste caso, todos com o valor padrão false ou, entre outras opções, realizar a inicialização informando um array de inteiros, como no caso da inicialização do objeto “ba1”. Lembrando que nossa coleção armazena bits, e que um inteiro possui 32 bits, o nosso objeto “ba1” tem como tamanho inicial – propriedade Lenght – o valor 64. Podemos também alterar o valor de todos os bits usando o método SetAll e pudemos também realizar o acesso aleatório a um bit desejado, alterando o seu valor.

Para operações com bits onde o tamanho não ultrapassar um inteiro de 32 bits, o ideal é que seja utilizada a estrutura System.Collections.Specialized.BitVector32, que oferece as mesmas funcionalidades que a classe BitArray, mas possui uma performance melhor por se tratar de um tipo por valor, enquanto a classe BitArray é um tipo por referência.


Coleções especializadas

Tratam-se de coleções construídas com propósitos altamente definidos, especializadas e com tipos fortemente definidos.

A classe System.Collections.Specialized.StringDictionary é um exemplo. Ela implementa uma hashtable com a chave fortemente tipada para ser um tipo String ao invés de um tipo Object. Vejamos o seu uso:

Shared Sub ExecStringDictionary()
Dim sd As New System.Collections.Specialized.StringDictionary
Console.WriteLine("Trabalhando com a coleção especializada StringDictionary.")
sd.Add("0", "Usando coleções com VB.NET")
sd.Add("1", "Wallace Santos")
Console.WriteLine("Número de elementos da coleção: {0}", sd.Count)
Dim i As Int32
For i = 0 To sd.Count - 1
Console.WriteLine("O Valor para a chave {0} é: {1}", i, sd.Item(i))
Next
End Sub

Listagem 5 – Acessando a classe System.Collection.Specialized.StringDictionary

A classe StringDictionary é preenchida exclusivamente por strings e, portanto, não possui um índice numérico. A localização de qualquer elemento dentro da coleção pode ser realizada através da verificação do valor da propriedade Keys, que implementa a interface ICollection (internamente utiliza uma classe Hashtable). É importante saber que a ordem das chaves e valores dos elementos não é especificado. Isso quer dizer que o resultado da execução da sentença For da listagem 5 não será necessariamente a mesma de entrada dos elementos.


Como escolher a opção correta

As opções para escolhermos a coleção a ser usada em cada situação são muitas. É necessário muito cuidado para esta tarefa, pois cada uma tem as suas próprias funcionalidades e limitações, restringindo o uso da coleção. Para escolher a coleção correta a ser utilizada, considere as seguintes questões:

  • Existe a necessidade de uma lista sequêncial, onde o elemento é tipicamente descartado após o valor ser retirado? Se a resposta for sim, considere o uso da Queue ou Stack;
  • Existe a necessidade de acessar os elementos numa certa ordem, do tipo primeiro a entrar, primeiro a sair, ou último a entrar, primeiro a sair, ou acesso randômico? Queue oferece acesso do tipo primeiro a entrar, primeiro a sair (first-in-first-out), Stack oferece acesso do tipo último a entrar, primeiro a sair (last-in-first-out). As demais coleções oferecem acesso randômico;
  • Existe a necessidade de acessar cada elemento por um índice? As classes ArrayList e StringCollection oferecem acesso a seus elementos através de indices baseado em zero (zero-based index). As coleções Hashtable, SortedList, ListDictionary e StringDictionary oferecem acesso a seus elementos através do uso da chave do elemento. As coleções NameObjectCollectionBase e NameValueCollection oferecem acesso através de uma combinação das opções já citadas;
  • Existe a necessidade de que cada elemento contenha um valor ou, a combinação de uma chave e um valor ou ainda uma combinação de uma chave e múltiplos valores? No caso de um valor, use qualquer coleção baseada na interface IList, no caso uma chave e um valor, use qualquer classe baseada na interface IDictionary e no caso de uma chave e múltiplos valores, utilize a classe NameValueCollection;
  • Existe a necessidade de classificar os elementos de forma diferente de como eles foram informados? Neste caso temos 3 opções: Hashtable, que classifica os elementos pelo código hash (sistema de codificação derivado do código ASCII) da chave, SortedList, que classifica os elementos pela sua chave, baseado na implementação da interface IComparer e ArrayList, que fornece um método de classificação (Sort) que recebe a interface IComparer como parâmetro;
  • Existe a necessidade de realizar buscas rápidas e retirar a informação? ListDictionary é mais rápido que Hashtable em pequenas coleções (dez itens ou menos);
  • e Existe a necessidade de coleções que aceitem somente strings? StringCollection (baseada na interface IList) e StringDictionary (baseada na interface IDictionary) são especializadas nessa tarefa.

Conclusão

As opções para escolhermos a coleção a ser usada em cada situação são muitas. Coleções são um recurso interessante e poderoso que temos nas nossas mãos. Seu uso deve ser consciente e agora podemos fazer isso.

Wallace Cézar Sales dos Santos

Wallace Cézar Sales dos Santos - Arquiteto de Software da Datasul S.A., responsável pelos sistemas que utilizam a tecnologia Microsoft .NET. Profissional certificado como MCSD (Microsoft Certified Solution Developer), MCP (Microsoft Certified Professional) e MVP (Microsoft Most Valuable Professional) e membro do INETA Speaker Boreau, sendo contribuidor de diversas comunidades de desenvolvedores nacionais que utilizam a tecnologia .Net. É também co-autor do livro Desenvolvendo com C#, lançado pela editora Bookman e está atualmente trabalhando num livro de Visual Basic .Net. Ele pode ser contactado por email ou MS Messenger.