Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .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).
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. Listing 2 | Eenvoudige array - 2 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
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"}
Listing 1 | Eenvoudige array - 1 Private Sub TestArray_1() ' Dimensioneer Var --> 0 = eerste element Dim strWerknemers(4) As String ' Vul de waarden strWerknemers(0) strWerknemers(1) strWerknemers(2) strWerknemers(3) strWerknemers(4)
= = = = =
"Andre" "Willem" "Jacob" "Jan" "Roel"
' Toon de 5e werknemer Console.WriteLine(strWerknemers(4)) Console.ReadLine() ' Wacht op toets End Sub
| VB Magazine Online | 2004 – 12 |
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 moge-
1 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
lijk 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’. Listing 3 | Twee-dimensionale array 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
Bovenstaande code resulteert in het volgende resultaat:
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. Listing 5 | Shared methods 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"}
Afbeelding 1 | Test twee-dimenionele array
' Met een For ...Next alle elementen tonen For intCounter = 0 To strWerknemers.Length - 1 Console.WriteLine(strWerknemers(intCounter)) Next
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!
Console.WriteLine("Gesorteerd..............")
Listing 4 | Properties en methods
' Maar een For...Each werkt ook For Each strNaam In strWerknemers Console.WriteLine(strNaam) Next
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
| VB Magazine Online | 2004 – 12 |
' Sorteer de array Array.Sort(strWerknemers)
Console.ReadLine()
' Wacht op een toets
End Sub
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()
Clear() Copy()
Zoekt met behulp van een ‘binary’ vergelijking naar een waarde in een 1dimensionele array. De array moet wel gesorteerd zijn. Wist de inhoud van de array. Kopieert (eventueel een deel) van de array naar een andere array.
2 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
CreateInstance()
IndexOf()
LastIndexOf()
Reverse()
Sort()
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. Retourneert de index van de gezochte waarde die het eerst voorkomt in de array. Deze method lijkt erg op de IndexOf() method, het retourneert echter de laatst voorkomende index van een gezochte waarde. Keert alle waarden in de array om. Wanneer u deze method direct aanroept na een Sort(), dan kan men dus ook aflopend sorteren. 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. Listing 6 | ArrayList-class 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")
| VB Magazine Online | 2004 – 12 |
' 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
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. Listing 7 | ArrayList van eigen objecten 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
3 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
Afbeelding 4 | Test ArrayList met eigen objecten
Interessante Properties .Capacity
.Count .Item
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 Retourneert het aantal elementen in de ArrayList. 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()
Add() AddRange()
BinarySearch()
Clear()
GetRange()
Insert() InsertRange()
Remove()
RemoveAt() RemoveRange() Reverse() SetRange() Sort()
TrimToSize()
Maakt een ArrayList wrapper om een IList object. Dit klinkt erg cryptisch en ik zal hier later op terugkomen. Voegt een element aan het einde van de ArrayList toe. Voegt een collectie van elementen (ICollection) aan het einde van de ArrayList toe. Zoekt een element aan de hand van het binaire zoek algoritme. ArrayList dient wel gesorteerd te zijn. Verwijdert alle elementen uit de ArrayList. Let op de capaciteit wordt hierdoor niet beïnvloed. Retourneert een ArrayList met een specifiek deel van de oorspronkelijke ArrayList. Voegt een element op een specifieke index toe aan de ArrayList. Voegt een collectie van elementen (ICollection) toe op een specifieke index. Verwijdert het eerste element van een specifiek object dat voorkomt in de ArrayList. Verwijdert een element op een specifieke index in de ArrayList. Verwijdert een range van elementen uit de ArrayList. Zet de elementen van de ArrayList in de omgekeerde volgorde. Overschrijft een range van elementen met een andere elementencollectie. 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. Past de capaciteit van de ArrayList aan aan het aantal elementen. Wanneer men minder dan 16 elementen heeft, wordt de capaciteit ingesteld op 16.
| VB Magazine Online | 2004 – 12 |
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 zoge-
4 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
naamde 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 | Gebruik van de Hashtable-class
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. U ziet dat Listing 9 | Toch niet contextgevoelig?
Listing 8 | Test Hashtable 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
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).
| VB Magazine Online | 2004 – 12 |
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
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: Listing 10 | Valkuil benaderen hashtable ‘per Index’ 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))
5 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
' 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
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…
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. Listing 11 | 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")
Afbeelding 7 | Waarden in hashtable worden benaderd per key
Interessante Properties .Count
.Item(key) Keys Values
Retourneert het aantal elementen dat de hashtable bevat, dus het aantal ‘keyvalue’ paren. Retourneert de waarde van een bepaalde key of stelt deze in. Retourneert een ICollection met daarin de alle keys van de hashtable. Retourneert een ICollection met daarin de alle waarden van de hashtable.
' 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
Interessante Methods Add(key, value) Clear() Contains(key) ContainsKey(key)
ContainsValue(Value) Remove(key)
Voegt een element toe aan de hashtable. Verwijdert alle elementen in de hashtable. Retourneert een booleaanse waarde of de key in de hashtable voorkomt. Retourneert een booleaanse waarde of de key in de hashtable voorkomt. Is dus hetzelfde als Contains(). Retourneert een booleaanse waarde of de waarde in de hashtable voorkomt 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.
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.
SortedList-class | VB Magazine Online | 2004 – 12 |
6 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
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
.Count .Item(key) Keys Values
Retourneert het aantal elementen dat de SortedList kan bevatten of stelt deze in. Zie: ArrayList. Retourneert het aantal elementen dat de SortedList bevat. Retourneert de waarde van een bepaalde key of stelt deze in. Retourneert een ICollection met daarin de alle keys van de SortedList. Retourneert een ICollection met daarin de alle waarden van de SortedList.
Interessante Methods Add(key, value) Clear() Contains(key) ContainsKey(key)
ContainsValue(Value) GetByIndex(Index) GetKey(Index) GetKeyList() GetValueList() IndexOfKey(Key) IndexOfValue(Value) Remove(key) RemoveAt(Index) SetByIndex(Index, Value) TrimToSize()
Voegt een element toe aan de SortedList. Verwijdert alle elementen in de SortedList. Retourneert een booleaanse waarde of de key in de SortedList voorkomt. Retourneert een booleaanse waarde of de key in de SortedList voorkomt. Is dus hetzelfde als Contains(). Retourneert een booleaanse waarde of de waarde in de SortedList voorkomt Retourneert een element op de specifieke index. Retourneert de key op een de specifieke index. Retourneert een IList met de keys uit de SortedList. Retourneert een IList met de waarden uit de SortedList. Retourneert de index van de opgegeven key. Retourneert de index van de opgegeven waarde. Verwijdert een element (met de desbetreffende key) uit de SortedList. Verwijdert een element (met de desbetreffende index) uit de SortedList. Vervangt een element op de opgegeven index 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
| VB Magazine Online | 2004 – 12 |
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… Listing 12 | Microsoft.VisualBasic.Collection 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
Door het afwijkende datatype wordt 1 en “1” anders geïnterpreteerd en resulteert in verwarrende resultaten.
Afbeelding 9 | Hoezo verwarrend? (1) of (“1”)….
7 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
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.
Not() Or(bitarray)
Set(index, boolean)
Listing 13 | Test BitArray-class 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
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 .Item(index) .Length
Retourneert het aantal elementen dat de BitArray bevat.. Retourneert de waarde van element of stelt deze in. Retourneert het aantal elementen dat de BitArray bevat of stelt deze in
SetAll(boolean) Xor(bitarray)
Inverteert alle waarden in de array, zodat True False wordt, en andersom Voert een bitsgewijze Or operatie uit op de elementen in de huidige BitArray ten opzichte van de meegegeven andere BitArray. Stel de waarde in van een specifiek element/ Stelt de waarden in van alle elementen in de 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…
Interessante Methods And(bitarrqy)
CopyTo( Get(index)
Voert een bitsgewijze And operatie uit op de elementen in de huidige BitArray ten opzichte van de meegegeven andere BitArray. Kopieert een BitArray naar een andere, 1-dimensionale Array. Retourneert de waarde van een element op een specifieke locatie.
| VB Magazine Online | 2004 – 12 |
Afbeelding 10 | Test Stack-class
8 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
Listing 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)
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.
' Alleen even de laatste waarde bekijken Console.WriteLine("Peek laatste waarde: " & objStack.Peek()) Console.WriteLine("Aantal: " & objStack.Count)
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.
' Maak Stack weer leeg Console.WriteLine("Pop laatste waarde: " & objStack.Pop()) Console.WriteLine("Pop laatste waarde: " & objStack.Pop())
Listing 15 | Queue-class
Console.ReadLine() ' Wacht op toets
Dim objQ As New Queue(3)
End Sub
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() Contains(value) Peek() Pop() Push() ToArray()
Private Sub TestQueue_1()
Verwijdert alle elementen uit de Stack. Retourneert True indien de gezocht waarde zich in de Stack bevindt. Retourneert de laatste waarde in de Stack, maar verwijdert het niet. Retourneert en verwijdert de laatste waarde in de Stack. Voegt een item bovenop de Stack toe. 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.
| VB Magazine Online | 2004 – 12 |
' 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
Bovenstaande code geeft het volgende resultaat:
Afbeelding 12 | Test de Queue-class
Interessante Properties .Count
Retourneert het aantal elementen in de Queue.
Interessante Methods Clear() Contains(value)
Verwijdert alle elementen uit de Queue. Retourneert True indien de gezocht waarde zich in de Queue bevindt.
9 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
Dequeue() Enqueue() Peek() ToArray()
Retourneert en verwijdert de eerste waarde in de Queue. Voegt een item aan het einde van de Queue toe. Retourneert de eerste waarde in de Queue, maar verwijdert het niet. Kopieert de Queue naar een nieuwe Array.
Stack
traag
aan einde
aan einde
Queue
traag
aan einde
aan begin
erg snel
flexibel
flexibel
Collection
(*) Het element zal gelijk worden gesorteerd.
System.Collections
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.
BitArray ICloneable
Queue
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.
ICloneable
Stack ICloneable
ArrayList ICloneable
IEnumerable
ICollection
IList
HashTable
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.
ISerializable ICloneable
IDictionary
SortedList ICloneable
(ReadOnly)CollectionBase is achterweg gelaten
Afbeelding 13 | Interface-afhankelijkheden van de classes 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.
nee
auto
traag
Collection
Zoeken
Toevoegen
Verwijderen
Array(structuren)
Class
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
| VB Magazine Online | 2004 – 12 |
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. U kunt de voorbeeldcode van (http://www.vbgroup.nl) downloaden.
onze
website
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)
10 / 11
Ü vbg.vbnet.beginner | Werken met arrays en andere collecties binnen Visual Basic .NET |
• • •
Applied .NET Framework programming VB.NET Jeffrey Richter en Francesco Balena (MS Press) http://www.codeproject.com/vb/net/SearchSortCo mparison.asp http://visualbasic.about.com/od/usingvbnet/l/aa07 1203a.htm
Wie is André Obelink? André Obelink (1969) is werkzaam als technical manager en consultant bij AcouSoft Informatisering B.V. AcouSoft is marktleider in Nederland op het gebied van software voor de audicienbranche. Daarnaast bouwt AcouSoft maatwerkapplicaties en ondersteunt zij andere bedrijven bij allerhande softwaretrajecten in VB6/VB.NET/MS Access 97/XP en SQL-Server 7/2000. André is een MCSD, programmeert in Visual Basic sinds 1991 (ja… versie 1.0 dus..) en behoort tot een van de oprichters van de Visual Basic Groep. Als hij niet programmeert, doceert, leest, schrijft of droomt over programmeren, brengt hij graag zijn tijd door met zijn vrouw en twee kinderen. U kunt hem bereiken via
[email protected] © 2004 - Copyright Visual Basic Groep. Dit artikel is auteursrechtelijk beschermd. Afdrukken voor eigen gebruik is toegestaan. http://www.vbgroup.nl
| VB Magazine Online | 2004 – 12 |
11 / 11