Registreren   Inloggen   Zoeken:  Nu zoeken
vrijdag 21 november 2008
     
Visual Basic Magazine 2004-12 | december 2004

Werken met arrays en collecties binnen VB.NET

Het .NET Framework is zeer goed toegerust op het gebruik van arrays en andere typen collecties. Het is, doordat er meerdere classes beschikbaar zijn, echter lastig overzicht te krijgen. Er zijn ondermeer Array-, BitArray-, Stack-, Queue-, ArrayList, HashTable en SortedList-classes. Ze hebben in basis hetzelfde doel, maar is het u duidelijk wanneer u wèlke class het beste kan gebruiken? In dit artikel maakt u kennis met deze classes en zal worden uitgelegd voor welk doel u de desbetreffende class het best kunt inzetten. Gaat u er maar eens goed voor zitten!

In de dagelijkse praktijk zijn er legio voorbeelden te bedenken die u als een array of een collectie zou kunnen definiëren. Denkt u maar eens een bedrijf waar een vijftal mensen werken en u wilt de namen van deze werknemers opslaan. Nu kunt u in uw programma voor elke werknemer een variabele aanmaken en de desbetreffende naam er aan toekennen. U moet dan alle namen van deze variabelen onthouden en u snapt waarschijnlijk ook wel dat het werken met die data ook er omslachtig is. Het is een eenvoudiger om een array of ander type collectie gebruiken. Arrays helpen u om gelijksoortige of –vormige data in een aangesloten geheugengebied te bewaren.


Array-class
De Array-class bevindt zich in de System namespace en om deze class te gebruiken hoeft u dus geen extra namespace te importeren. U kunt eenvoudig een array maken door de variabele te dimensioneren en met behulp van de haakjes '(x)' om zo aan te geven dat het om een array van die variabele gaat. U hoeft niet direct het aantal elementen op te geven, dus hoe groot de array moet worden. Dit kunt u ook achteraf doen. In Listing 1 wordt een array gemaakt van het type String. Het eerste element heeft als index 0, dus wanneer we, zoals in dit geval, een array willen maken met 5 elementen, dan definiëren we deze als MijnVariabele(4).


 Private Sub TestArray_1()

    ' Dimensioneer Var --> 0 = eerste element
    Dim strWerknemers(4) As String

    ' Vul de waarden
    strWerknemers(0) = "Andre"
    strWerknemers(1) = "Willem"
    strWerknemers(2) = "Jacob"
    strWerknemers(3) = "Jan"
    strWerknemers(4) = "Roel"

    ' Toon de 5e werknemer
    Console.WriteLine(strWerknemers(4))
    Console.ReadLine() ' Wacht op toets

 End Sub

Codevoorbeeld 1. Eenvoudige array - 1

Dit voorbeeld laat goed zien hoe en waar de data is opgeslagen. Een wat ervarener ontwikkelaar zal de dimensioneren van de variabele en het toekennen van de waarden waarschijnlijk op één regel doen. Het resultaat is exact hetzelfde als in Listing 1, het is uiteraard veel minder code, maar het is zeker niet intuïtiever of beter te begrijpen.


 Private Sub TestArray_2()

    ' Dimensioneer Var --> 0 = eerste element
    Dim strWerknemers() As String = {"Andre", "Willem", _
                                     "Jacob", "Jan", "Roel"}
    ' Toon de 5e werknemer
    Console.WriteLine(strWerknemers(4))
    Console.ReadLine() ' Wacht op toets

 End Sub

Codevoorbeeld 2. Eenvoudige array - 2

De techniek uit Listing 2 is bijvoorbeeld handig wanneer u een array gebruikt als parameter in een functie of bij het toekennen van een property op een object. Een voorbeeld:


  objTest.ValueArray = New String() {"Andre", "Willem", "Roel"}

Men hoeft dan niet eerst een tijdelijk variabele aan te maken. Wat wellicht ook opvalt is dat het aantal elementen niet hoeft te worden opgegeven; dit kan namelijk bepaald worden aan de hand van het aantal elementen die zijn gebruikt bij het initialiseren van de variabele.

In bovenstaande voorbeelden hebben we arrays gemaakt die slechts uit één dimensie bestaan. Het is echter ook heel goed mogelijk om meerder dimensies te definiëren en zelfs arrays van arrays worden native ondersteund.

Ik zal de properties en methods van de array-class bespreken aan de hand van een twee-dimensionele array. Dit type array kan men vergelijken met een tabel; een tabel bestaat uit kolommen en rijen. Ik ga er vanuit dat in elke kolom een gelijk aantal elementen zitten. Technisch gezien hoeft dit echter niet het geval te zijn, het is namelijk goed mogelijk dat de eerste kolom tien elementen heeft en bijvoorbeeld de tweede kolom slechts 5.

In ons voorbeeld wil ik naast de naam ook de woonplaats van de werknemer opslaan. De 'tabel' bestaat dus uit twee kolommen namelijk 'Naam' en 'Woonplaats' en twee rijen met de waarden voor zowel 'André' en 'Willem'.


Private Sub TestArray_3()
' Dimensioneer Var --> let op 2 dimensionaal + 2 rijen Dim strWerknemer(,) As String = {{"Andre", "Winterswijk"}, _ {"Willem", "Wijk bij Duurstede"}}

' Toon de naam + woonplaats 2e werknemer Console.WriteLine(strWerknemer (1, 0) & " woont in " & _ strWerknemer (1, 1)) Console.ReadLine() ' Wacht op toets
End Sub
Codevoorbeeld 3. Twee-dimensionale array 

Bovenstaande code resulteert in het volgende resultaat:

Afbeelding 1


Afbeelding 1. Test twee-dimenionele array

Doordat het in al onze voorbeelden allemaal arrays betreft hebben we tegelijkertijd de beschikking over een aantal properties en methods waarmee we informatie over onze array kun opvragen. In een niet multidimensionale array kunt u het aantal rijen met de .Length property opvragen, maar bij een multidimensionale geeft het het aantal elementen terug, in onderstaand geval dus zes!


 Private Sub TestArray_4()

   ' Dimensioneer Var --> let op 2 dimensionaal + 3 rijen
   Dim strTest(,) As String = {{"Andre", "Winterswijk"}, _
                               {"Willem", "Wijk bij Duurstede"}, _
                               {"Jan", "Zwolle"}}

   ' Toon eigenschappen van de array
    Console.WriteLine("Index eerste rij: " & _
                       strTest.GetLowerBound(0))
    Console.WriteLine("Index laatste rij: " & _
                       strTest.GetUpperBound(0))
    Console.WriteLine("Aantal rijen: " & _
                      strTest.GetUpperBound(0) - _
                      strTest.GetLowerBound(0) + 1)
    Console.WriteLine("Aantal kolommen: " & strTest.Rank)
    Console.WriteLine("Aantal elementen: " & strTest.Length)
    Console.ReadLine() ' Wacht op toets

 End Sub

Codevoorbeeld 4. Properties en methods

 

Afbeelding 2


Afbeelding 2. Properties en methods array variabele


Array-clas | Shared methods
De Array-class kent ook een aantal shared methods. Deze methods behoren tot de class en niet een object-instantie ervan. Dit houdt ondermeer in dat bij het aanroepen van deze mehods, de array nog als parameter dient te worden meegegeven. Hoewel het zou kunnen, zou je deze methods kunnen aanroepen op je variabele. Het ligt echter meer voor de hand om ze te gebruiken via de Array-class. In het voorbeeld hieronder wordt de array gesorteerd. Tevens kan men in dit voorbeeld zien, dat men zowel met een 'For..Next' als een 'For..Each' loop door alle elementen kan lopen.


 Private Sub TestArray_5()
' Dimensioneer Var --> 0 = eerste element Dim intCounter As Int32 Dim strNaam As String Dim strWerknemers() As String = {"Andre", "Willem", _ "Jacob", "Jan", "Roel"} ' Met een For ...Next alle elementen tonen For intCounter = 0 To strWerknemers.Length - 1 Console.WriteLine(strWerknemers(intCounter)) Next
Console.WriteLine("Gesorteerd..............")
' Sorteer de array Array.Sort(strWerknemers)
' Maar een For...Each werkt ook For Each strNaam In strWerknemers Console.WriteLine(strNaam) Next
Console.ReadLine() ' Wacht op een toets
End Sub
Codevoorbeeld 5. Shared methods

Op de Clear() method na kennen alle shared methods minimaal drie overloaded methods; het voert daarom te ver om in onderstaande overzicht elke method in al haar varianten uitvoerig te bespreken. De beschrijving van de method is dus vrij globaal.

BinarySearch() Zoekt met behulp van een 'binary' vergelijking naar een waarde in een 1-dimensionele array. De array moet wel gesorteerd zijn.
Clear() Wist de inhoud van de array.
Copy() Kopieert (eventueel een deel) van de array naar een andere array.
CreateInstance() Maak een nieuwe instantie van de Array-class. Hiermee zou men eventueel de lowerbound van de array kunnen laten afwijken van 0; dit is echter af te raden om in de toekomst verwarring te verkomen.
IndexOf() Retourneert de index van de gezochte waarde die het eerst voorkomt in de array.
LastIndexOf() Deze method lijkt erg op de IndexOf() method, het retourneert echter de laatst voorkomende index van een gezochte waarde.
Reverse() Keert alle waarden in de array om. Wanneer u deze method direct aanroept na een Sort(), dan kan men dus ook aflopend sorteren.
Sort() Sorteert (een deel van) de elementen in de array.

De functionaliteit van de Array-class is voor veel doeleinden ruimschoots voldoende. Een nadeel is dat het achteraf toevoegen van een element wat omslachtiger is. U zult de array moeten redimensioneren met het keyword Preserve (zodat de bestaande inhoud bewaard blijft). In het laatste, dus hoogste element kunt u vervolgens de nieuwe waarde plaatsen. Wat ik persoonlijk ook een nadeel vind, is dat men de elementen alleen op een index kan benaderen. Wanneer de array uit unieke elementen bestaat, kan men het item nog wel snel vinden met behulp van de IndexOf() method.


ArrayList-class
De ArrayList-class is een soort kruising tussen een array en een Collection zoals u die misschien uit Visual Basic 6 kent. Aan de ene kant kunt werken met data zoals in een array, aan de andere heeft u de voordelen dat u eenvoudig elementen kunt toevoegen op een bepaalde locatie, kunt verwijderen en de elementen niet alleen per index kunt benaderen. Een variabele van het type ArrayList heeft een initiële grootte. Op zich hoeft u zich hierover geen zorgen te maken, de ArrayList past automatisch de grootte aan. Uit oogpunt van performance is het wel raadzaam om initieel de 'beste' grootte te kiezen. Wanneer u geen grootte aangeeft, bestaat de ArrayList in eerste instantie uit 16 elementen. Het werken met een ArrayList is erg eenvoudig en u heeft een aantal methods tot uw beschikking die erg intuïtief werken.


 Private Sub TestArrayList_1()
Dim strWerknemers As New ArrayList(5)
' Voeg 2 werknemers toe.. strWerknemers.Add("Andre") strWerknemers.Add("Willem")
' Voeg 'Piet' toe op positie 1 (0-based) strWerknemers.Insert(1, "Piet") ' Voeg een collectie toe op een bepaalde positie ' in dit geval op de laatste --> we hadden ' hier dus ook AddRange() kunnen gebruiken strWerknemers.InsertRange(3, strWerknemers) ' Verwijder een item op Index, we halen dus ' 'Piet' weer uit de collectie strWerknemers.RemoveAt(1) ' Verwijder nu ook het andere element 'Piet' ' nu niet per index maar per key strWerknemers.Remove("Piet")
End Sub
Codevoorbeeld 6. ArrayList-class

 

Afbeelding 3


Afbeelding 3. Resultaat test ArrayList-class

Dat u een ArrayList ook kunt gebruiken voor objectcollecties van uw classes bewijst het volgende voorbeeld. U dient bij het bestuderen van de onderstaande code in uw achterhoofd te houden dat er wel een class 'Werknemer' gedefinieerd moet zijn. In dit geval kent deze class twee public properties, namelijk .Naam en .Woonplaats. Omwille van de ruimte is de definitie van deze class hier niet afgedrukt, maar u kunt hem vinden in het voorbeeldproject dat u van onze site kunt downloaden.


Private Sub TestArrayList_2()
Dim objWerknemer As Werknemer Dim objWerknemers As New ArrayList(2)
' Maak twee objecten aan van het type 'Werknemer' ' de definitie hiervan bevindt zich de classmodule Dim objWerknemer1 As New Werknemer("Andre", "Winterswijk") Dim objWerknemer2 As New Werknemer("Willem", _ "Wijk bij Duurstede")
' Voeg deze object toe aan de ArrayList objWerknemers.Add(objWerknemer1) objWerknemers.Add(objWerknemer2)
' Doorloop de Werknmers en toon naam + woonplaats For Each objWerknemer In objWerknemers Console.WriteLine(objWerknemer.Naam & " woont in " & _ objWerknemer.Woonplaats) Next
' Toon aantal items Console.WriteLine("Aantal items: " & objWerknemers.Count) Console.ReadLine() ' Wacht op toets
End Sub
Codevoorbeeld 7. ArrayList van eigen objecten 

 

Afbeelding 4


Afbeelding 4. Test ArrayList met eigen objecten

Interessante Properties
.Capacity Retourneert het aantal elementen dat de ArrayList kan bevatten of stelt deze in. U kunt bijvoorbeeld de ArrayList exact de juiste capaciteit geven door: ArrayList.Capacity = ArrayList.Count
.Count Retourneert het aantal elementen in de ArrayList.
.Item Retourneert een element op en specifieke index of stelt deze in. Dit is ook een soort default property, want: ArraList(0) retourneert exact hetzelfde als ArrayList.Item(0)

Interessante Methods
Adapter() Maakt een ArrayList wrapper om een IList object. Dit klinkt erg cryptisch en ik zal hier later op terugkomen.
Add() Voegt een element aan het einde van de ArrayList toe.
AddRange() Voegt een collectie van elementen (ICollection) aan het einde van de ArrayList toe.
BinarySearch() Zoekt een element aan de hand van het binaire zoek algoritme. ArrayList dient wel gesorteerd te zijn.
Clear() Verwijdert alle elementen uit de ArrayList. Let op de capaciteit wordt hierdoor niet beïnvloed.
GetRange() Retourneert een ArrayList met een specifiek deel van de oorspronkelijke ArrayList.
Insert() Voegt een element op een specifieke index toe aan de ArrayList.
InsertRange() Voegt een collectie van elementen (ICollection) toe op een specifieke index.
Remove() Verwijdert het eerste element van een specifiek object dat voorkomt in de ArrayList.
RemoveAt() Verwijdert een element op een specifieke index in de ArrayList.
RemoveRange() Verwijdert een range van elementen uit de ArrayList.
Reverse() Zet de elementen van de ArrayList in de omgekeerde volgorde.
SetRange() Overschrijft een range van elementen met een andere elementencollectie.
Sort() Sorteert de elementen. Bij een ArrayList die bestaat uit bijvoorbeeld eigen objecten, dan wordt sorteren ingewikkelder. Op welke property moet gesorteerd worden? Men moet dan aan de slag met zogenaamde Comparerobjecten. Het voert nu te ver om hier dieper op in te gaan.
TrimToSize() Past de capaciteit van de ArrayList aan aan het aantal elementen. Wanneer men minder dan 16 elementen heeft, wordt de capaciteit ingesteld op 16.

In het bovenstaande overzicht zijn niet alle properties en methods besproken. De ArrayList kent in principe ook de dezelfde properties en methods als de eerder besproken Array-class. Ook kwam de method Adapter() aan de orde. Deze shared methode verwacht één parameter, namelijk een object dat is afgeleid van IList, en maakt daar als het ware een 'wrapper-object' omheen. Bij IList-objecten kunt u denken aan items in een Listbox of Combobox. Met het wrapper-object kunt vervolgens allerlei trucs uithalen, want u heeft namelijk de beschikking of alle properties en methods die de ArrayList-class ook heeft. De Adapter() method maakt geen kopie van die variabele aan, maar kapselt zich echt om het originele IList-object heen. Methode aanroepen op de ArrayList hebben dus ook direct invloed op het IList-object. Een voorbeeld zal waarschijnlijk een en ander verduidelijken.


 Dim objAdapter As ArrayList

 objAdapter = ArrayList.Adapter(Combobox1.ListItems)
 objAdapter.Reverse()
 

Met deze code draait u de volgorde van alle items in een Combobox om. U zou echter ook items kunnen toevoegen, verwijderen of sorteren. De mogelijkheden zijn legio, want alle besproken methods staan in principe tot uw beschikking.

De ArrayList-class is erg veelzijdig en in veel gevallen vriendelijker dan de gewone Array-class. Ikzelf pas in veel gevallen deze class toe, omdat ik hem erg prettig in gebruik vindt.


Hashtable-class
Wanneer u in het verleden, dus in Visual Basic 6, gebruik heb gemaakt van het Dictionary-object, dan zal u deze class zeker bekend voorkomen. Deze class implementeert namelijk de IDictionary-interface. Alle objecten die afgeleid zijn van IDictionary gebruiken in principe twee series van variabelen, de 'values' en de 'keys'. U gebruikt de key om vervolgens de waarde (value) weer op te halen. Deze class is hiervoor geoptimaliseerd en zal dus voor dit soort scenario's de beste performance geven. Wanneer een 'keyvalue' paar wordt toegevoegd aan de Hashtable object, dan wordt de positie van het element in de interne array gebaseerd op de numerieke hash van de 'key'. Bij het zoeken van het element (dus key) wordt er weer gebruik gemaakt van die numerieke hash. Het element kan razendsnel gelokaliseerd worden, zonder 'intern' alle elementen te hoeven doorlopen. U kunt elk object als key gebruiken, want achter de schermen wordt de GetHashCode() method gebruikt om de hash te genereren. Deze method is afgeleid van System.Object, de moeder van alle variabelen en datatypes.

Er bestaat de kans, afhankelijk van hoe de hashcode wordt geëvalueerd, dat er meerdere keys naar dezelfde positie verwijzen. We spreken dan van een collision. Maakt u geen zorgen, dit wordt netjes voor u geregeld, het kan echter wel de performance beïnvloeden. Met behulp van de zogenaamde LoadFactor kunt u de kans op de collision verkleinen, maar dit houdt wel in dat u concessies doet aan performance. De performance kunt u positief beïnvloeden, door, net als bij de ArrayList-class, te kiezen voor een zo'n 'goed' mogelijke begincapaciteit. U kunt zelfs bepalen of wilt dat de hash door een andere provider wordt gegenereerd. In praktijk heb ik echter nog nooit LoadFactors of HashCodeProviders hoeven gebruiken.

Afbeelding 5


Afbeelding 5. Gebruik van de Hashtable-class

 


 Private Sub TestHashTable_1()

    Dim hstLanden As New Hashtable(3)

    ' Voeg elementen toe
    hstLanden.Add("NL", "Nederland")
    hstLanden.Add("BE", "België")
    hstLanden.Add("DE", "Duitsland")

    ' Toon het land 'BE'
    Console.WriteLine("BE: " & hstLanden("BE"))
    ' Keys aanwezig?
    Console.WriteLine("NL in lijst: " & hstLanden.ContainsKey("NL"))
    Console.WriteLine("DK in lijst: " & hstLanden.ContainsKey("DK"))

    ' Waarde aanwezig? Contextgevoelig?
    Console.WriteLine("'Duitsland' in lijst: " & _
                          hstLanden.ContainsValue("Duitsland"))
    Console.WriteLine("'DUITSLAND' in lijst: " & _
                          hstLanden.ContainsValue("DUITSLAND"))

    ' Verwijder Belgie
    hstLanden.Remove("BE")

    ' Toon aantal items
    Console.WriteLine("Aantal items: " & hstLanden.Count)
    Console.ReadLine() ' Wacht op toets

 End Sub 

Codevoorbeeld 8. Test Hashtable

De HashTable is uitermate geschikt voor zogenaamde 'Lookup-tables'. Het kan elementen razendsnel terugvinden in de collectie. U kunt ook numerieke waarden als key gebruiken, waardoor het benaderen van de waarden in de hashtable erg veel lijkt op het benaderen van elementen in een normale array, bijvoorbeeld hstTest(0).

Afbeelding 6


Afbeelding 6. Resultaat Test HashTable

Het is belangrijk te onthouden dat het zoeken naar de key of value contextgevoelig is. Zie in dit licht het codevoorbeeld (Listing 8) naar de waarde 'Duitsland' en 'DUITSLAND'. De een wordt wel gevonden, de ander dus niet. Wanneer u de waarde probeert in te stellen van een item dat niet kan worden gevonden, dan wordt het automatisch toegevoegd.

Wilt u echter toch een hashtable waarbij de key niet contextgevoelig wordt gezocht, dan kunt u uitwijken naar de CreateCaseInsensitiveHashtable() method op de Specialized.CollectionUtil-class.


 Private Sub TestHashTable_2()
Dim hstLanden As Hashtable = _ Specialized.CollectionsUtil.CreateCaseInsensitiveHashtable(2)
' Voeg landen toe hstLanden.Add("NL", "Nederland") hstLanden.Add("BE", "België")
' Key aanwezig? Contextgevoelig? Console.WriteLine("'NL' in lijst: " & _ hstLanden.ContainsKey("NL")) ' = True Console.WriteLine("'nl' in lijst: " & _ hstLanden.ContainsKey("nl")) ' = True Console.ReadLine() ' Wacht op toets
End Sub
Codevoorbeeld 9. Toch niet contextgevoelig?

Zoals al eerder vermeld kunt u ook numerieke waarden gebruiken als key. U moet zich niet laten verleiden om te denken dat dit dan de index betreft. Dit hoeft absoluut niet zo te zijn! Bekijkt u de code in Listing 10 maar eens:


 Private Sub TestHashTable_3()
Dim objDE As DictionaryEntry Dim hstTest As New Hashtable(3)
' Voeg werknmers toe met een bepaalde key hstTest.Add(2, "Andre") hstTest.Add(1, "Willem") hstTest.Add(0, "Jacob")
' Toon de werknmers op basis van Key --> Het lijkt ' alsof we de hashtable per Index benaderen Console.WriteLine("hstTest(0) = " & hstTest(0)) Console.WriteLine("hstTest(1) = " & hstTest(1)) Console.WriteLine("hstTest(2) = " & hstTest(2)) ' Doorloop nu de hashtable van begin tot eind For Each objDE In hstTest Console.WriteLine(objDE.Value) Next
Console.ReadLine() ' Wacht op toets
End Sub
Codevoorbeeld 10. Valkuil benaderen hashtable 'per Index'

Het lijkt erop dat we de hashtable benaderen op basis van de Index. Het element hstTest(0) bevindt zich niet perse op de eerste positie. Waarschijnlijk bevindt het zich als laatste item in de hashtable. Dit hoeft echter niet zo te zijn; de 'fysieke' volgorde van de elementen in de hashtable is onvoorspelbaar en eigenlijk ook niet van belang. U moet er echter wel voor waken om code te schrijven die hier vanuit gaat. Veel ontwikkelaars zijn al in deze valkuil getrapt...

Afbeelding 7


Afbeelding 7. Waarden in hashtable worden benaderd per key

Interessante Properties
.Count Retourneert het aantal elementen dat de hashtable bevat, dus het aantal 'keyvalue' paren.
.Item(key) Retourneert de waarde van een bepaalde key of stelt deze in.
Keys Retourneert een ICollection met daarin de alle keys van de hashtable.
Values Retourneert een ICollection met daarin de alle waarden van de hashtable.

Interessante Methods

Add(key, value) Voegt een element toe aan de hashtable.
Clear() Verwijdert alle elementen in de hashtable.
Contains(key) Retourneert een booleaanse waarde of de key in de hashtable voorkomt.
ContainsKey(key) Retourneert een booleaanse waarde of de key in de hashtable voorkomt. Is dus hetzelfde als Contains().
ContainsValue(Value) Retourneert een booleaanse waarde of de waarde in de hashtable voorkomt.
Remove(key) Verwijdert een element (met de desbetreffende key) uit de collectie.

De hashtable-class is een erg krachtig gereedschap wanneer u met 'key-value' paren moet werken. Het mist de mogelijkheden die bijvoorbeeld de ArrayList-class heeft met betrekking tot sorteren, omkeren, zoeken, bepaalde ranges doorzoeken, enzovoort. De class is er immers ook niet voor bedoeld. Het heeft slecht één doel: u zo snel mogelijk de waarde geven behorende bij de opgegeven key.


SortedList-class
Begint het u al te duizelen? Zijn er meer vragen dan antwoorden? Ik kan me er iets bij voorstellen. Maakt u echter toch nog uw borst nat. Het vlaggenschip van de 'collectionachtige' objecten moet nog besproken worden: de SortedList-class. Het object lijkt enerzijds veel op de hashtable, maar het beschikt ook over de functionaliteit om alle elementen gesorteerd te houden. Standaard worden alle items gesorteerd op de key. Om u goed het verschil te laten zien tussen de HashTable-class en de SortedList-class heb ik de code uit Listing 10 omgezet naar het gebruik van de SortedList-class.


 Private Sub TestSortedList_1()
Dim objDE As DictionaryEntry Dim slsTest As New SortedList(3)
' Voeg werknmers toe met een bepaalde key slsTest.Add(2, "Andre") slsTest.Add(1, "Willem") slsTest.Add(0, "Jacob")
' Toon de werknmers op basis van Key --> Het lijkt ' alsof we de sortedlist bij Index benaderen Console.WriteLine("slsTest(0) = " & slsTest(0)) Console.WriteLine("slsTest(1) = " & slsTest(1)) Console.WriteLine("slsTest(2) = " & slsTest(2))
' Doorloop nu de hashtable van begin tot eind For Each objDE In slsTest Console.WriteLine(objDE.Value) Next
Console.ReadLine() ' Wacht op toets
End Sub
 
Codevoorbeeld 11. SortedList-class

 

Afbeelding 8


Afbeelding 8. Automatisch sorteren SortedList-class

Vergelijk afbeelding 7 en afbeelding 8. U ziet dat de items gesorteerd zijn op basis van de key. Zoals u ook al weet kan de key ook een variabele van het type String zijn, waardoor u het kan laten sorteren waarop u maar wilt, mits de keys maar uniek zijn. Dit is niet nodig voor het sorteren, maar u kunt nu eenmaal geen dubbele keys gebruiken in de SortedList (en HashTable en …) Wilt u toch nog sorteren op andere waarden dan zult u aan de slag moet met objecten die afgeleid zijn van ICompare en minimaal de methode Compare() implementeren. Hiermee kunt u bijvoorbeeld een SortedList met Werknemer-objecten laten sorteren op de Woonplaats-property. Het voert nu echter te ver om dit verder uit te werken, maar op het Internet zijn een aantal voorbeelden te vinden. Het klinkt ingewikkelder dan het feitelijk is.

Het sorteren van de keys gebeurd standaard contextgevoelig. U kunt dit standaard gedrag overriden door wederom de Specialized.CollectionUtil-class te gebruiken. In tegenstelling tot het eerdere voorbeeld bij de HashTable roept u nu de CreateCaseInsenstiveSortedList() method aan. De syntax is verder hetzelfde. Het aantal methods waarover de SortedList beschikt overtreffen alle ander classes. Een aantal op een rij:

Interessante Properties
Capacity Retourneert het aantal elementen dat de SortedList kan bevatten of stelt deze in. Zie: ArrayList.
.Count Retourneert het aantal elementen dat de SortedList bevat.
.Item(key) Retourneert de waarde van een bepaalde key of stelt deze in.
Keys Retourneert een ICollection met daarin de alle keys van de SortedList.
Values Retourneert een ICollection met daarin de alle waarden van de SortedList.

Interessante Methods
Add(key, value) Voegt een element toe aan de SortedList.
Clear() Verwijdert alle elementen in de SortedList.
Contains(key) Retourneert een booleaanse waarde of de key in de SortedList voorkomt.
ContainsKey(key) Retourneert een booleaanse waarde of de key in de SortedList voorkomt. Is dus hetzelfde als Contains().
ContainsValue(Value) Retourneert een booleaanse waarde of de waarde in de SortedList voorkomt.
GetByIndex(Index) Retourneert een element op de specifieke index.
GetKey(Index) Retourneert de key op een de specifieke index.
GetKeyList() Retourneert een IList met de keys uit de SortedList.
GetValueList() Retourneert een IList met de waarden uit de SortedList.
IndexOfKey(Key) Retourneert de index van de opgegeven key.
IndexOfValue(Value) Retourneert de index van de opgegeven waarde.
Remove(key) Verwijdert een element (met de desbetreffende key) uit de SortedList.
RemoveAt(Index) Verwijdert een element (met de desbetreffende index) uit de SortedList.
SetByIndex(Index, Value) Vervangt een element op de opgegeven index
TrimToSize() Maak de capaciteit gelijk aan het aantal elementen. (.Capacity = .Count)

Performance

U begrijpt vast zeker wel dat al deze functionaliteit ook een keerzijde heeft. Francesco Balena heeft voor zijn boek 'Programming MS Visual Basic .NET 2003' een test gedaan met een routine die 100.000 elementen toevoegt aan de collectie en daarna de performance van de ArrayList, HashTable en SortedList met elkaar vergeleken.

De ArrayList was 4 maal sneller dan de HashTable. De HashTable was vervolgens weer 8 tot 100 maal sneller dan de SortedList. Deze cijfers zijn natuurlijk niet helemaal representatief en onder alle omstandigheden valide, maar het geeft toch wel een indruk. Het moet u echter wel aan het denken zetten of u daadwerkelijk zo'n zware class als de SortedList nodig heeft.


Microsoft.VisualBasic.Collection-class
Om de verwarring nog groter te maken: wanneer u de referentie naar Microsoft.VisualBasic niet expliciet verwijderd heeft, heeft u standaard ook nog de mogelijkheid om een variabele aan te maken van het type Collection. Deze class is exact hetzelfde als de Collection-class zoals u die wellicht uit Visual Basic 6 kent. Hoewel de class heerlijk overzichtelijk is, met alleen de properties en methods die u echt nodig heeft en hoewel de class ook geweldig performed en bijna net zo flexibel is als de HashTable-class.... moet u hem weer snel vergeten. Daar zijn eigenlijk een aantal redenen voor te noemen. Persoonlijk vind ik de belangrijkste reden dat uw code niet meer compatible is met bij bijvoorbeeld C#. Daarnaast heeft de Collectionclass de nare eigenschap te beginnen bij 1 en dus niet bij 0 zoals alle andere soortgelijke datatypen binnen het .NET Framework wel beginnen. Als laatste kunnen er erg makkelijk fouten optreden doordat de key of index anders wordt geïnterpreteerd. Bestudeert u onderstaande code eens goed en u weet precies wat ik bedoel. Gewoon niet meer gebruiken dus...


 Private Sub TestCollection_1()

   Dim colWerknemers As New Collection

   ' Maak objecten aan
   Dim objWerknemer1 As New Werknemer("Andre", "Winterswijk")
   Dim objWerknemer2 As New Werknemer("Willem", _
                                      "Wijk bij Duurstede")

   ' Voeg de objecten toe aan de collectie
   ' Add(object, [key], ..)
   colWerknemers.Add(objWerknemer1, "2")
   colWerknemers.Add(objWerknemer2, "1")

   ' Microsoft.VisualBasic.Collection begint bij 1!
   ' Zonder quotes ==> zoeken op Index --> Andre
   Console.WriteLine(CType(colWerknemers(1), Werknemer).Naam)

   ' Met quotes --> zoeken op Key --> Willem
   Console.WriteLine(CType(colWerknemers("1"), Werknemer).Naam)
   Console.ReadLine() ' Wacht op toets

 End Sub
 
Codevoorbeeld 12. Microsoft.VisualBasic.Collection

Door het afwijkende datatype wordt 1 en "1" anders geïnterpreteerd en resulteert in verwarrende resultaten.

Afbeelding 9


Afbeelding 9. Hoezo verwarrend? (1) of ("1")....


BitArray-class
De BitArray-class is ontwikkeld om op een zeer optimale manier te kunnen werken met arrays, waarbij de elementen uit slechts één bit bestaan. In feit is het dus een array met enkel en alleen booleaanse waarden.

 
 Private Sub TestBitArray_1()

   ' Maak een BitArray met alle waarden True
   Dim btaTest As New BitArray(16, True)

   ' Zet eerste element op False
   btaTest.Set(0, False)

   ' Toon 1e + 2e element met Get() en Item()
   Console.WriteLine("Get(0) :" & btaTest.Get(0))
   Console.WriteLine("Item(1):" & btaTest.Item(1))

   ' Inverteer in een keer hele array.. True wordt
   ' False, False wordt True
   btaTest.Not()

   ' Toon 1e + 2e element met Get() en Item()
   Console.WriteLine("Get(0) :" & btaTest.Get(0))
   Console.WriteLine("Item(1):" & btaTest.Item(1))

   ' Ook For..Each werkt ook --> tel aantal False
   Dim intFalseCount As Int32
   Dim blnCurrentValue As Boolean

   For Each blnCurrentValue In btaTest
      If blnCurrentValue = False Then intFalseCount += 1
   Next

   ' Print dus 15...
   Console.WriteLine("Er zijn {0} waarden False", intFalseCount)
   Console.ReadLine() ' Wacht op toets

 End Sub 

Codevoorbeeld 13. Test BitArray-class

De class kent een aantal methods die het werken met bits en bitsgewijze vergelijken een stuk eenvoudiger maken, zoals Not(), And(), Or() en Xor(). Ook kan men BitArrays eenvoudig instantiëren op basis van een andere BitArray. Virtueel gezien bestaat er geen maximum aantal elementen.

Interessante Properties
.Count Retourneert het aantal elementen dat de BitArray bevat.
.Item(index) Retourneert de waarde van element of stelt deze in.
.Length Retourneert het aantal elementen dat de BitArray bevat of stelt deze in.

Interessante Methods
And(bitarrqy) Voert een bitsgewijze And operatie uit op de elementen in de huidige BitArray ten opzichte van de meegegeven andere BitArray.
CopyTo() Kopieert een BitArray naar een andere, 1-dimensionale Array.
Get(index) Retourneert de waarde van een element op een specifieke locatie.
Not() Inverteert alle waarden in de array, zodat True False wordt, en andersom.
Or(bitarray) Voert een bitsgewijze Or operatie uit op de elementen in de huidige BitArray ten opzichte van de meegegeven andere BitArray.
Set(index, boolean) Stel de waarde in van een specifiek element.
SetAll(boolean) Stelt de waarden in van alle elementen in de BitArray.
Xor(bitarray) Voert een bitsgewijze eXclusive Or operatie uit op de elementen in de huidige BitArray ten opzichte van de meegegeven andere BitArray.

BitVector32-class
In tegenstelling tot de BitArray-class, kent de BitVectorclass een beperking van 32 elementen. Ook kan kan de BitVector32-class waarden (integers) opslaan tot 32 bits. Het is met name handige wanneer u moet werken met bitgecodeerde velden. In praktijk wordt dit vaak gebruikt om bepaalde hardware aan te sturen.

Omdat het element een element opgebouwd kan zijn uit 'secties' groter dan één bit, moet men zogenaamde sections definiëren. Men dient dat de doen met de CreateSection() method. De eerste parameter bevat de maximale integerwaarde (om de grootte van de sectie te bepalen) en de tweede parameter een eventueel vorige sectie.

Het feit dat deze class zich in de namespace System.Collection.Specialized bevindt, geeft eigenlijk al aan dat dit een bijzondere class is; een class die waarschijnlijk door een zeer kleine groep mensen gebruikt zal worden. Daarom laat ik in dit artikel het voorbeeld achterwege. U kunt het echter wel terugvinden in het voorbeeldproject (TestBitVector32_1()).


Stack-Class
De Stack-class is bedoeld om zogenaamde last-in-first-out (LIFO) principes te implementeren. Het lijkt op het bouwen van een toren. Telkens wordt er een blokje op de toren gelegd en eventueel wordt de toren ook weer blokje voor blokje afgebroken. Binnen Visual Basic 6 werkt mijn algemene ErrorHandling-component op dezelfde wijze. Bij binnenkomst in een procedure wordt de procedurenaam op de stack 'gepusht' en bij het verlaten van de procedure weer van de stack 'gepopt', dus er weer afgehaald. Hierdoor kun je bij eventuele fouten precies zien welke weg je code gegaan is om daar te belanden waar het nu een fout tegenkomt. De CallStack-window binnen Visual Studio .NET werkt overigens op dezelfde wijze. Ik verdenk Microsoft ervan dat ze dat van mij hebben afgekeken...

Afbeelding 10


Afbeelding 14. Test Stack-class


 Private Sub TestStack_1()
Dim objStack As New Stack(3)
' Voeg items toe --> 3 items objStack.Push("Procedure A") objStack.Push("Procedure B") objStack.Push("Procedure C")
' Haal het laatste item eraf Console.WriteLine("Aantal: " & objStack.Count) Console.WriteLine("Pop laatste waarde: " & objStack.Pop()) Console.WriteLine("Aantal: " & objStack.Count)
' Alleen even de laatste waarde bekijken Console.WriteLine("Peek laatste waarde: " & objStack.Peek()) Console.WriteLine("Aantal: " & objStack.Count)
' Maak Stack weer leeg Console.WriteLine("Pop laatste waarde: " & objStack.Pop()) Console.WriteLine("Pop laatste waarde: " & objStack.Pop())
Console.ReadLine() ' Wacht op toets
End Sub
Codevoorbeeld 14. Test Stack-class

Het principe is simpel. U voegt een element toe met de method Push(). U haalt de data op en verwijdert deze met Pop(). Wilt u alleen de waarde bekijken dan gebruikt u Peek().

Interessante Properties
Count Retourneert het aantal elementen in de Stack.

Interessante Methods

Clear() Verwijdert alle elementen uit de Stack.
Contains(value) Retourneert True indien de gezocht waarde zich in de Stack bevindt.
Peek() Retourneert de laatste waarde in de Stack, maar verwijdert het niet.
Pop() Retourneert en verwijdert de laatste waarde in de Stack.
Push() Voegt een item bovenop de Stack toe.
ToArray() Kopieert de Stack naar een nieuwe Array.

Queue-class

Iedereen die vroeger economie in zijn of haar vakkenpakket had zitten, herinnert zich waarschijnlijk nog wel dat er naast het eerder genoemde LIFO principe ook nog een FIFO principe bestaat: first-in-first-out. Hiervoor kunt u de Queue-class gebruiken. Het werkt als een soort buffer. Aan de achterkant van de queue wordt telkens nog wat toegevoegd, terwijl men aan de voorkant van de queue hard zijn best doet om de queue of wachtrij weg te werken. U kunt het het beste vergelijken met een lopende band, waarbij de werknemers aan het einde de producten inpakken en dat machines nieuwe producten blijven aanvoeren. Of misschien wat dichter bij huis, uw printerwachtrij op uw eigen computer.

Afbeelding 11


Afbeelding 11. Dimensioneer een Queue

Wanneer u een Queue aanmaakt dient u een initiële capaciteit op te geven en eventueel een waarde waarmee de queue zich moet vergroten, indien de maximale grootte bereikt is. Het vergroten van de queue gebeurd overigens volledig automatisch en u hoeft zich daar geen zorgen over te maken.

De werking van Queue-class is ook erg eenvoudig en lijkt sterk op de Stack-class. De Push()- en de Pop()-method zijn vervangen door de Enqueue()- en Dequeue()-method.


 Private Sub TestQueue_1()
Dim objQ As New Queue(3)
' Voeg items toe --> 3 items objQ.Enqueue("Product A") objQ.Enqueue("Product B") objQ.Enqueue("Product C")
' Haal het eerste item eraf Console.WriteLine("Aantal: " & objQ.Count) Console.WriteLine("Dequeue 1e waarde : " & objQ.Dequeue()) Console.WriteLine("Aantal: " & objQ.Count)
' Alleen even de nieuwe eerste waarde bekijken Console.WriteLine("Bekijk 1e waarde: " & objQ.Peek()) Console.WriteLine("Aantal: " & objQ.Count)
' Maak Queue leeg Console.WriteLine("Dequeue 1e waarde : " & objQ.Dequeue()) Console.WriteLine("Dequeue 1e waarde : " & objQ.Dequeue()) Console.ReadLine() ' Wacht op toets
End Sub
Codevoorbeeld 15. Queue-class

Bovenstaande code geeft het volgende resultaat:

Afbeelding 12


Afbeelding 12. Test de Queue-class

Interessante Properties
.Count Retourneert het aantal elementen in de Queue.

Interessante Methods
Clear() Verwijdert alle elementen uit de Queue.
Contains(value) Retourneert True indien de gezocht waarde zich in de Queue bevindt.
Dequeue() Retourneert en verwijdert de eerste waarde in de Queue.
Enqueue() Voegt een item aan het einde van de Queue toe.
Peek() Retourneert de eerste waarde in de Queue, maar verwijdert het niet.
ToArray() Kopieert de Queue naar een nieuwe Array.

Zowel de Stack- als de Queue-class heb ik in praktijk nog nooit toegepast. Ik kan me echter goed voorstellen, bijvoorbeel in asynchrone processen, dat een Queue-class erg handig kan zijn.


Samenvattend
In zekere zin heb ik geprobeerd volledige te zijn. Ik heb geprobeerd u duidelijk te maken wat de globale werking van de verschillende classes zijn en in welke situaties u die classes het beste zou kunnen toepassen. Vanuit dat oogpunt is dit artikel redelijk volledig. Daar tegenover staat dat ik alle besproken methods, zowel in dit artikel als in het voorbeeldproject, slechts heel summier heb belicht. Ik heb mij gericht op de core-functionaliteit, maar bijna alle methods hebben een aantal overloads, en bijna alle classes kennen meerdere constructors. Alleen over dit onderwerp kan men een boek schrijven.

Om de diverse classes een beetje met elkaar te kunnen vergelijken en om snel inzicht te krijgen in de sterke en zwakke punten van die class, zet ik de meest voor de hand liggende globaal naast elkaar. De gekozen waarden voor ondermeer performance van het sorteren en zoeken zijn niet verkregen door eigen testprocedures. Ik heb ze verkregen via bronnen op het Internet en naar mijn mening benaderen ze wel goed de werkelijkheid. Er zullen echter absoluut voorbeelden te bedenken zijn, waarin de getoonde waarden niet geheel reëel zijn.

Class Duplicaten Vergroten Sorteren
Array(structuren) ja lastig goed
Array(objecten, 2) ja lastig traag
ArrayList ja auto goed
SortedList nee auto auto
HashTable nee auto auto
Stack ja auto n.v.t.
Queue ja auto n.v.t.
Collection nee auto traag

Class Zoeken Toevoegen Verwijderen
Array(structuren) traag lastig lastig
Array(objecten, 2) traag lastig lastig
ArrayList snel flexibel flexibel
SortedList snel n.v.t. (*) flexibel
HashTable snelst n.v.t. (*) flexibel
Stack traag aan einde aan einde
Queue traag aan einde aan einde
Collection erg snel flexibel flexibel

(*) Het element zal gelijk worden gesorteerd.

Afbeelding 13


Afbeelding 13. Interface-afhankelijkheden van de classes

In bovenstaande afbeelding kunt u zien welke interfaces de besproken classes implementeren. Wanneer u de kenmerken van de interfaces IList, ICollection en IEnumerable kent, en u weet welke classes deze implementeren, dan bent u voor een groot deel in staat om het gedrag van alle besproken classes te verklaren. Want in feite zijn het deze drie interfaces die het bepalen.


Bronnen

  • Programming MS Visual Basic .NET v2003 - Francesco Balena (Microsoft Press) ;
  • Programmeren met Visual Basic .NET - David Grundgeiger (O'Reilly/Academic Service) ;
  • Visual Basic .NET - Het complete handboek - Bill Evjen, Jason Beres e.a (Academic Service) ;
  • MS Visual Basic .NET - Programmers Cookbook - Mathew MacDonald (Microsoft Press) ;
  • Applied .NET Framework programming VB.NET - Jeffrey Richter en Francesco Balena (MS Press)

Succes!

André

Advertenties
Leer ook Visual Basic!

Visual Basic 2005 - de Basis | André Obelink

Bestanden bij artikel
 


Microsoft Certified Solution Developer

Microsoft Certified Professional

Microsoft Most Valuable Professional

VP Speakers Bureau - INETA Europe

Brainbench Certified Master Visual Basic 6.0 Programmer

Gebruiksovereenkomst   Privacybeleid   Copyright 2005 André Obelink