Clique para classificar e enviar comentários
MSDN
Biblioteca MSDN
Artigos Técnicos
.NET Framework
.NET Framework 1.1
 .NET Collections - Interfaces

  Ativar exibição de largura de banda baixa
.NET Collections - Interfaces

por Israel Aéce

Aplica-se:

  • .NET Framework 1.1

  • VB.NET e C#

As coleções são grupos de objetos similarmente iguais que são agrupados, quais podemos manipular, inserindo, removendo, alterando e exibindo. No .NET Framework Class Library temos um Namespace específico qual contem diversas coleções para serem utilizadas em nossas aplicações. Este Namespace chama-se System.Collections.

Dentro deste Namespace, encontramos várias classes e Interfaces para a criação dos mais diversos tipos de coleções. As coleções estão definidas em três categorias: Coleções Ordenadas, Coleções Indexadas e Coleções baseadas em uma "chave-e-valor". Veremos abaixo a finalidade de cada uma delas:

  • Coleções Ordenadas - São coleções que são distingüidas apenas pela ordem de inserção e assim controla a ordem em que os objetos podem ser recuperados da coleção. System.Collections.Stack e System.Collections.Queue são exemplos deste tipo de coleção.

  • Coleções Indexadas - São coleções que são distingüidas pelo fato de que seus valores e/ou objetos podem ser recuperados/acessados através de um índice numérico (baseado em 0 (zero)). System.Collections.ArrayList é um exemplo deste tipo de coleção.

  • Coleções "Chave-e-Valor" - Como o próprio tipo diz, é uma coleção que contém uma chave associado a algum tipo. Seus índices são classificados no valor da chave e podem ser recuperados na ordem classificado pela enumeração. System.Collections.HashTable é um exemplo deste tipo de coleção.

  • Além das coleções, temos dentro do Namespace System.Collections as interfaces genéricas - tema deste artigo - que são implementadas nas várias classes de coleções que aqui temos. Estas Interfaces se fazem necessárias, pois são implementadas para garantir um "contrato", obrigando assim, uma classe à implementar os métodos e propriedades que são necessários àquelas funcionalidades.

As Interfaces

As Interfaces que são utilizadas para serem implementadas nas coleções do Namespace System.Collections ou mesmo criar nossas próprias coleções são: ICollection, IComparer, IDictionary, IDictionaryEnumerator, IEnumerable, IEnumerator e IList.

Cada uma destas Interfaces contém métodos e propriedades que obrigatóriamente devem ser implementados nas classes que as implementarem. Abaixo veremos a hierarquia de alguma destas Interfaces:

Cc518002.f01(pt-br,MSDN.10).jpg

Figura 1 - Hierarquia entre algumas das Interfaces.

Como todas as Interfaces (consequentemente as coleções também, direta ou indiretamente) devem implementar a Interface IEnumerable e extendendo a Interface ICollection, você pode escolher entre duas implementações: IList ou IDictionary, onde IList é uma coleção onde podemos recuperar seus objetos dado um índice e IDictionary uma coleção onde definimos chave-e-valor. Veremos um poucos mais sobre estas Interfaces um pouco mais abaixo.

Na Figura 1, vemos que as Interfaces IList e IDictionary são derivadas (herdam) de ICollection e esta por sua vez, herda da Interface IEnumerable.

Veremos a partir de agora, um pouco mais sobre cada Interface que é utilizada para implementações das coleções intrínsicas do .NET Framework e também podendo ser usadas para a criação de coleções customizadas.

IEnumerable e IEnumerator

Como vemos na Figura 1, a Interface IEnumerable é a base direta e indiretamente para algumas outras Interfaces e todas as coleções a implementam. Esta interface tem apenas um método a ser implementado, chamado GetEnumerator, qual retorna um objeto do tipo da Interface de IEnumerator. Antes de falarmos deste método, primeiramente, veremos do se trata a Interface IEnumerator.

A Interface IEnumerator é a base para todos os enumeradores. Composta por uma propriedade (Current) e dois métodos (MoveNext e Reset), percorre e lê os elementos de uma determinada coleção, permitindo apenas ler os dados, não podendo alterá-los. Vale levar em consideração que enumeradores não podem ser utilizados para a coleção subjacente.

A propriedade Current nos retorna o valor corrente que está sendo lido. Consequentemente deverá chamar o método MoveNext para avançar o enumerador antes de ler o valor corrente. Já o método Reset restaura à posição inicial da coleção.

Para ficar um pouco mais claro, veremos o exemplo em código:

Public Class TestIEnumerator

    Implements IEnumerator

    Private _categorias() As String = {"ASP.NET", "VB.NET", "C#", "SQL", "XML"}
    Private _current As Integer = -1

    Public ReadOnly Property Current() As Object Implements System.Collections.IEnumerator.Current
        Get
            If Me._current < 0 Then
                Throw New InvalidOperationException
            ElseIf Me._current > 4 Then
                Throw New InvalidOperationException
            Else
                Return Me._categorias(Me._current)
            End If
        End Get
    End Property

    Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
        Me._current += 1
        If Me._current > 4 Then
            Return False
        Else
            Return True
        End If
    End Function

    Public Sub Reset() Implements System.Collections.IEnumerator.Reset
        Me._current = -1
    End Sub

End Class

Como podemos ver, os métodos Reset e MoveNext e também a propriedade Current deve ser implementados para conseguirmos a iteração na coleção, que neste caso está sendo feito em uma Array de String, denomidado _categorias. E abaixo o código que é escrito no cliente para invocar esta classe:

    Sub Main()

        Dim test As New TestIEnumerator
        While test.MoveNext()
            Console.WriteLine(test.Current())
        End While

    End Sub

Pois bem, depois de entedermos a Interface IEnumerator, voltaremos agora a discutir o método GetEnumerator da Interface IEnumerable, que retorna um objeto IEnumerator. A Interface IEnumerable é utilizado em classes onde poderá ser recuperado seus elementos através do laço For Each.

Geralmente quando a Interface IEnumerable é implementada em uma determinada classe, o método GetEnumerator retornará uma instância de uma classe privada que implementa a Interface IEnumerator. Mas claro que isso pode variar de acordo com a sua necessidade. O código ficará da seguinte forma implementando a Interface IEnumerable:

Public Class TestIEnumerable

    Implements IEnumerable

    Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
        Return New TestIEnumerator
    End Function

End Class

E no cliente, criamos a instância da classe e percorremos a coleção através do laço For Each:

    Sub Main()

        Dim testIEnumerable As New testIEnumerable
        For Each categoria As Object In testIEnumerable
            Console.WriteLine(categoria)
        Next

    End Sub

Pois bem, depois de entedermos a Interface IEnumerator, voltaremos agora a discutir o método GetEnumerator da Interface IEnumerable, que retorna um objeto IEnumerator. A Interface IEnumerable é utilizado em classes onde poderá ser recuperado seus elementos através do laço For Each.

Geralmente quando a Interface IEnumerable é implementada em uma determinada classe, o método GetEnumerator retornará uma instância de uma classe privada que implementa a Interface IEnumerator. Mas claro que isso pode variar de acordo com a sua necessidade. O código ficará da seguinte forma implementando a Interface IEnumerable:

Public Class TestIEnumerable

    Implements IEnumerable

    Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
        Return New TestIEnumerator
    End Function

End Class

E no cliente, criamos a instância da classe e percorremos a coleção através do laço For Each:

    Sub Main()

        Dim testIEnumerable As New testIEnumerable
        For Each categoria As Object In testIEnumerable
            Console.WriteLine(categoria)
        Next

    End Sub

ICollection

A Interface ICollection é a Interface base para todas as coleções que estão contidas dentro do Namespace System.Collections. Direta ou indiretamente essas classes a implementa.

Ela por sua vez é composta por três propriedades (Count, IsSynchronized e SyncRoot) e um método (CopyTo). Abaixo veremos a utilidade de cada um desses membros:

Membro

Descrição

Count

Quando implementado pela classe, retorna o número de elementos contidos na ICollection.

IsSynchronized

Quando implementado pela classe, indica se o acesso à ICollection é sincronizado (thread-safe).

SyncRoot

Quando implementado pela classe, retorna um objeto que pode ser usado para sincronizar o acesso à ICollection.

CopyTo

Quando implemento pela classe, copia os elementos da ICollection para um Array, iniciando em um índice particular do Array.

Interfaces como IList e IDictionary derivam desta Interface ICollection, pois estas Interfaces são mais especializadas, tendo também outros métodos a serem definidos além dos quais já estão contidos dentro da Interface ICollection. Já as classes como System.Collections.Queue e System.Collections.Stack implementam a Interface ICollection diretamente.

IList

Derivada das Interfaces ICollection e IEnumerable ela é a Interface base para todas as listas contidas no .NET Framework. Listas quais podemos acessar individualmente por um índice seus objetos.

As implementações da Interface IList podem estar em das seguintes categorias: Read-Only, Fixed-Size e Variable-Size. Uma IList Read-Only, não permite que você modifique os elementos. Já uma IList Fixed-Size, não permite adicionar ou remover os elementos, mas permite que você altere os elementos existentes. E por fim, uma IList Variable-Size, qual permite que você adicione, remova ou altere os elementos.

Claro que quando implementamos esta Interface em alguma classe, é necessário criarmos um membro privado que será o responsável por armazenar os items. Este membro privado é manipulado pelos métodos e propriedades da Interface IList que serão implementados nesta classe.

Abaixo um exemplo de como implementar a Interface IList em uma classe. Vale lembrar que muitos métodos e propriedades foram ocultados por questões de espaço.

Public Class TestIList

    Implements IList

    Private _arr As ArrayList

    Public Sub New()
        Me._arr = New ArrayList
    End Sub

    Public ReadOnly Property Count() As Integer Implements System.Collections.ICollection.Count
        Get
            Return Me._arr.Count()
        End Get
    End Property

    Public Function Add(ByVal value As Object) As Integer Implements System.Collections.IList.Add
        Me._arr.Add(value)
    End Function

    '...

End Class

É importante ressaltar que no código acima é criado um membro privado chamado de _arr do tipo ArrayList e que sua manipulação é feita através dos métodos que estão definidos na Interface IList e nesta classe implementados.

Com a Interface ILIst conseguimos criar uma coleção customizada, podendo inclusive criar uma coleção fortemente tipada, onde seus tipos seriam únicos, ou seja, seria aceito apenas um objeto de um tipo definido e assim em design-time já conseguiríamos detectar possíveis problemas de "cast".

O código acima é apenas um exemplo para entedermos o funcionamento da implementação da Interface IList, onde a implementamos e criamos um membro privado do tipo ArrayList para armazenar os objetos, pois dentro do Namespace System.Collections já temos um classe abstrata, chamada CollectionBase, que é a base para a criação de coleções fortemente tipadas. Como a criação de coleções não é o foco deste artigo, fica aqui uma referência à um artigo que já escrevi sobre a classe CollectionBase, que encontra-se publicado no Portal Linha de Código.

IDictionary

A Interface IDictionary é a base para todas as coleções do tipo "chave-e-valor". Cada elemento inserido neste tipo de coleção é armazenado em um objeto do tipo DictionaryEntry (Structure).

Cada associação deve ter um chave original que não seja uma referência nula, mas o seu respectivo valor de associação pode incluir sem problemas uma referência nula.

Como já vimos um pouco acima quando falávamos sobre a Interface IList, as coleções "chave-e-valor" também estão divididas em três categorias: Read-Only, Fixed-Size e Variable-Size.

Dentro da Interface IDictionary temos os seguintes membros:

Membro

Descrição

Item

Quando implementado pela classe, retorna ou define um elemento baseando-se em uma "key".

Keys

Quando implementado pela classe, retorna um objeto do tipo ICollection, contendo as Keys do IDictionary.

Values

Quando implementado pela classe, retorna um objeto do tipo ICollection, contendo os Values do IDictionary.

Add

Quando implementado pela classe, adiciona um novo elemento no IDictionary, baseando na Key e Value que são informados como parâmetro para este método.

Ocultamos aqui alguns métodos e propriedades por questões de espaço, mas para uma lista completa, pode consultar o seguinte endereço: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemcollectionsidictionarymemberstopic.asp.

Uma classe bastante conhecida no mundo .NET que implementa a Interface IDictionary é a classe chamada Hashtable, que é uma coleção "chave-e-valor" e que organiza seus elementos baseando-se no código Hash da chave ("key").

Esta classe possui internamente uma Structure (estrutura de dados) que é composta pelos seguintes campos: hash_coll, key e val. Neste caso, temos um membro privado chamado bucktes, que nada mais é que um Array desta estrutura, onde são armazenados os valores que são inseridos através do método Add.

Como sabemos, para a utilização de um laço For Each para recuperar os valores de qualquer coleção, necessitamos saber o tipo do elemento para que isso seja possível. Para isso, o objeto que temos que utilizar para percorrer a coleção é a Structure DictionaryEntry. Abaixo, um exemplo de código, utilizando Hashtable:

Public Class TestIDictionary

    Private _hastTable As Hashtable

    Public Sub New()
        Me._hastTable = New Hashtable
        Me._hastTable.Add("qwert", "qwert")
        Me._hastTable.Add("asdfg", "asdfg")
        Me._hastTable.Add("zxcvb", "zxcvb")
    End Sub

    Public Sub PrintValues()
        For Each dictionary As DictionaryEntry In Me._hastTable
            Console.Write(dictionary.Key & " - " & dictionary.Value)
            Console.WriteLine()
        Next
    End Sub

End Class

Para invocar a classe no cliente, o código fica:

Module Module1

    Sub Main()

        Console.WriteLine("Teste com a Interface IDictionary.")
        Dim testIDictionary As New testIDictionary
        testIDictionary.PrintValues()
        Console.WriteLine()

    End Sub

End Module

Só para constar, o "output" deste código será:

Teste com a Interface IDictionary.
qwert - qwert
zxcvb - zxcvb
asdfg - asdfg 

Isto confirma o que vimos acima, onde não importa a ordem que o objeto foi inserido no Hashtable, pois internamente é aplicado um algoritmo para recuperar o valor da Hash "Key" para ter uma ordenação eficiente dos elementos.

IDictionaryEnumerator

Esta Interface tem a mesma finalidade da Interface IEnumerator, ou seja, percorrer e ler os elementos de uma determinada coleção, mas esta em especial, trata de enumerar os elementos de um dicionário, ou seja, uma coleção que contenha "chave-e-valor".

Para nos certificarmos disso, ao decompilar a propriedade "Current", qual está definida na Interface IEnumerator, pois IDictionaryEnumertator herda desta Interface, teremos a seguinte código:

Public Overridable ReadOnly Property Current As Object
      Get
            '...
            '...
            Return New DictionaryEntry(Me.currentKey, Me.currentValue)
      End Get
End Property

Como vimos anteriormente, cada elemento de uma coleção da classe Hashtable (qual está sendo discutida como exemplo) é representado por um objeto do tipo DictionaryEntry e neste caso, vemos que a propriedade Current retorna um objeto deste tipo, contendo seus respectivos valores (Key e Value).

Neste exemplo, a Interface IDictionaryEnumerator está implementanda dentro de uma classe privada chamada HashtableEnumerator qual é responsável por gerar o enumerador do Hashtable corrente, e retornando através da propriedade Current um objeto do tipo DictionaryEntry.

IComparer

Esta Interface é utilizada para customizar uma ordenação ("sort") em uma determinada coleção. A Interface IComparer é composta por um método chamado Compare, qual compara dois objetos e retorna um valor inteiro indicando se os objetos são ou não iguais, ou seja, se os valores comparados forem iguais, 0 (zero) é retornado.

Há classes, como por exemplo o ArrayList, que já implementa o método Sort para tal ordenação, mas há casos em que desejamos criar uma ordenação customizada, onde a ordenação padrão não nos interessa. É justamente neste momento que a Interface IComparer é utilizada, inclusive o método Sort da classe ArrayList tem um Overload que recebe no parâmetro um objeto do tipo da Interface IComparer.

Quando utilizamos o método Sort do ArrayList sem informar nenhum parâmetro, a ordenação é feita em ordem ascendente, a padrão. Mas podemos querer ordenar em ordem descendente, e com isso teríamos que criar uma classe que implementa a Interface IComparer, codificando assim o método Compare.

Vejamos abaixo um exemplo onde criamos duas classes chamadas: TestIComparerAscending e TestIComparerDescending onde cada uma delas é responsável para ordernar em um formato específico (Ascendente ou Descendente):

Public Class TestIComparerAscending

    Implements IComparer

    Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
        Return String.Compare(x.ToString(), y.ToString())
    End Function

End Class


Public Class TestIComparerDescending

    Implements IComparer

    Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
        Return String.Compare(y.ToString(), x.ToString())
    End Function

End Class 

E para constatar as suas funcionalidades, veja como fica o código no cliente, onde informamos o tipo de ordenação que desejamos:

Module Module1

    Sub Main()

        Console.WriteLine("Teste com a Interface IComparer.")
        Dim arr As New ArrayList
        arr.Add("NNNNN")
        arr.Add("AAAAA")
        arr.Add("ZZZZZ")
        arr.Add("BBBBB")
        arr.Add("IIIII")

        Console.WriteLine("Ordem Ascendente")
        arr.Sort(New TestIComparerAscending)
        For i As Integer = 0 To arr.Count - 1
            Console.WriteLine(arr.Item(i))
        Next
        Console.WriteLine()

        Console.WriteLine("Ordem Descendente")
        arr.Sort(New TestIComparerDescending)
        For i As Integer = 0 To arr.Count - 1
            Console.WriteLine(arr.Item(i))
        Next
        Console.WriteLine()

    End Sub

End Module

Quando utilizamos coleções customizadas, como por exemplo coleções derivadas de CollectionBase, a forma de implementação é um pouco diferente, ou seja, é criado uma(s) classe(s) privada dentro da classe que manipula a coleção, onde implementamos a Interface IComparer e definimos por qual propriedade do objeto/elemento que desejamos ordenar.

No caso do exemplo que mostrarei abaixo, apenas reescrevi o método ToString() na classe Usuário, retornando o nome do Usuário apenas para o exemplo. Quando necessitarmos fazer mais de um tipo de ordenação é necessário fazer o "cast" dos objetos x e y do método Compare para resgatarmos o valor da propriedade que desejamos ordenar, para que seja possível efetuar a comparação. Com isso, devemos nos atentar para que na coleção seja inserido somente um determinado tipo de objeto, pois se isso não ocorrer, uma Exception será atirada, pois não será possível efetuar o "cast".

Vejamos abaixo como ficaria a coleção para ordená-la:

Public Class UsuarioColecao

    Inherits CollectionBase

    Public Enum Ordenacao
        NomeCrescente
        NomeDecrescente
    End Enum

    Public Sub Sort(ByVal sorter As Ordenacao)
        Select Case sorter
            Case Ordenacao.NomeCrescente
                InnerList.Sort(New AscendingNameSorter)
            Case Ordenacao.NomeDecrescente
                Innerlist.Sort(New DescendingNameSorter)
        End Select
    End Sub

    Private Class AscendingNameSorter

        Implements IComparer

        Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
            Return String.Compare(x.ToString(), y.ToString())
        End Function

    End Class

End Class

Como vemos no código acima, a classe UsuarioColecao herda de CollectionBase, que como já vimos, utilizamos para a criação de uma coleção customizada. Vale lembrar que internamente (dentro da classe CollectionBase) existe um membro, do tipo ArrayList, e como sabemos que ele comporta o método Sort, apenas criamos o mesmo método na nossa coleção customizada de Usuários e passamos um instância da classe privada (que implementa a Interface IComparer) para que a ordenação seja realizada.

E finalmente, o código no cliente que faz o uso desta classe:

Module Module1

    Sub Main()

        Dim coll As New UsuarioColecao

        coll.Add(New Usuario("Joao"))
        coll.Add(New Usuario("Maria"))
        coll.Add(New Usuario("Willian"))
        coll.Add(New Usuario("Anna"))

        coll.Sort(UsuarioColecao.Ordenacao.NomeCrescente)
        For i As Integer = 0 To coll.Count - 1
            Console.WriteLine(coll.Item(i).Nome)
        Next
        Console.WriteLine()

        coll.Sort(UsuarioColecao.Ordenacao.NomeDecrescente)
        For i As Integer = 0 To coll.Count - 1
            Console.WriteLine(coll.Item(i).Nome)
        Next
        Console.WriteLine()

    End Sub

End Module

Ao chamarmos o método Sort da classe UsuarioColecao, informamos no parâmetro através de um enumerador qual é o tipo de ordenação que deverá ser realizada, e após isso, a ordenação é efetuada baseando-se na instância da classe privada que informamos no método Sort.

Conclusão

Vimos neste artigo as Interfaces que estão contidas dentro do Namespace System.Collections, analisando suas propriedades, métodos e entendendo suas funcionalidades nas classes que estão contidas dentro deste mesmo Namespace. Elas são utilizadas por todo o .NET Framework e também estão à disposição para a criação de coleções customizadas que possam vir a serem necessárias durante a construção de uma aplicação.

Sobre o autor:

Israel Aéce (israel@projetando.net) é Desenvolvedor de aplicações .NET, Microsoft MVP (Most Valuable Professional), Fundador e Líder do Grupo Projetando.NET (http://www.projetando.net), Colunista .NET e Moderador das Listas VB/ASP do Portal Linha de Código e e INETA LatAm Volunteer.

© 2009 Microsoft Corporation. Todos os direitos reservados. Termos de Uso  |  Marcas Comerciais  |  Política de Privacidade
Page view tracker