Visual Basic 2012 Voorbeelden
   

visual basic 2012 broncode voorbeelden

Blijf op de hoogte van de recente aanpassingen op vbvoorbeelden!

Microsoft Visual Studio 2012Microsoft Developers Network - Visual BasicMicrosoft .NET Framework

9.8. Collecties - Inleiding

Print Email Deel op Twitter Deel op Facebook

Dit artikel is gepubliceerd op maandag 15 oktober 2012 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.

Regelmatig komt het voor dat men in applicaties wil werken met een verzameling (een collectie) van objecten.  Net zoals we arrays kunnen creëren van Integers of Strings (of eender welk type) kunnen we ook een array aanmaken van een user-defined referencetype.
Hieronder zien we een voorbeeld van een array met elementen van het type Person.
Visual Basic 2012 Broncode - Codevoorbeeld 246
Class Person
    Public Property Name As String
End Class
Module Client1
    Dim persons As Person()
    Dim personsCount As Integer
    Sub Main()
        Dim person1 As Person = New Person
        person1.Name = "John"
        '
        Dim person2 As Person = New Person
        person2.Name = "Jane"
        '
        Console.WriteLine(personsCount)
        '
        AddPerson(person1)
        AddPerson(person2)
        '
        Console.WriteLine(personsCount)
        '
        Console.WriteLine(persons(1).Name)
        '
        Console.ReadLine()
    End Sub
    Sub AddPerson(ByVal item As Person)
        ReDim Preserve persons(personsCount)
        persons(personsCount) = item
        personsCount += 1
    End Sub
End Module
Console Application Output
0
2
Jane
De client is hier zelf verantwoordelijk voor het opstellen en definiëren van de logica omtrent het beheer van deze collectie van Person objecten.  Zo heeft de client hier bijvoorbeeld zelf moeten bepalen (implementeren) wat er juist dient te gebeuren wanneer een Person object wordt toegevoegd aan de verzameling.

De mogelijkheid bestaat ook deze logica weg te abstraheren voor de clients, dit bijvoorbeeld door collectie objecten te creëren.  Met onder andere als bedoeling ervoor te zorgen dat de clients zich hierbij niet meer hoeven bezig te houden.

Collectie objecten (gedefinieerd door collectietypen) kunnen ook beschouwd worden als een toepassing van containment.  Deze collectietypen kunnen het eenvoudiger maken voor clients met een collectie van objecten om te gaan.
Visual Basic 2012 Broncode - Codevoorbeeld 247
Class Persons
    Private _Items As Person()
    Default Public ReadOnly Property Item(ByVal index As Integer) As Person
        Get                                                                ' (1)
            Item = _Items(index)
        End Get
    End Property
    Private _Count As Integer
    Public ReadOnly Property Count() As Integer
        Get
            Count = _Count
        End Get
    End Property
    Public Sub Add(ByVal item As Person)
        ReDim Preserve _Items(Count)
        _Items(Count) = item
        _Count += 1
    End Sub
End Class
Module Client2
    Sub Main()
        Dim person1 As Person = New Person
        person1.Name = "John"
        '
        Dim person2 As Person = New Person
        person2.Name = "Jane"
        '
        Dim persons1 As Persons = New Persons
        '
        Console.WriteLine(persons1.Count)
        '
        persons1.Add(person1)
        persons1.Add(person2)
        '
        Console.WriteLine(persons1.Count)
        '
        Console.WriteLine(persons1(1).Name)                                ' (2)
        Console.WriteLine(persons1.Item(1).Name)                           ' (3)
        '
        Console.ReadLine()
    End Sub
End Module
Module Client3
    Sub Main()
        Dim person1 As Person = New Person
        person1.Name = "John"
        '
        Dim persons1 As Persons = New Persons
        persons1.Add(person1)
    End Sub
End Module
Console Application Output
0
2
Jane
Jane
Meerdere clients (zoals bovenstaande Client2 en Client3) kunnen nu deze collectielogica herbruiken, zonder dat die steeds opnieuw herhaald wordt.

Bemerk hoe de identifier van het collectietype (PersonS) duidelijk aangeeft dat een collect object van dat type meerdere Person objecten kan bevatten (meervoud gebruikt voor de identifier).

9.8.1. Indexer Property

Regel (1) geef de getter aan van wat men noemt een "indexer".  Dit is een typische member voor collectietypen waarvan de elementen via een index benaderd kunnen worden.  Deze indexer levert een Person object op (vandaar de verwijzing naar het type Person in de As clausule van die membersignatuur.
Meteen is hier ook aangegeven dat een Property net zoals procedures en functies argumentvariabelen kan gebruiken.  Alle mogelijkheden omtrent argumenten (meerdere argumenten, doorgeven van argumentwaarden by reference of by value, optionele argumenten, parameterarrays, ...) zijn hier ook beschikbaar.
Hier werd ervoor ge-opteerd de indexer readonly te maken, dit is echter niet noodzakelijk, afhankelijk van de vereisten kan men deze property ook writeable maken.

9.8.2. Default Property

Het keyword Default (hier gebruikt voor de indexer) zorgt ervoor dat men in de client niet expliciet meer hoeft te verwijzen naar de identifier van de property (zoals in regels (2) en (3) wordt geïllustreerd).  Meteen na de objectexpressie (gevormd door de identifier van de objectvariabele) kan men de argumentenlijst plaatsen.
Default kan enkele gebruikt worden bij properties met argumenten.  Dat (niet optionele) argumenten noodzakelijk zijn is eigenlijk logisch : in het geval dat men naar een argumentloze property zou kunnen verwijzen zonder de identifier op te geven, bekomt men gewoon de identifier van de objectvariabele, waarbij de compiler niet meer zou weten of het nu een verwijzing is naar het object of naar de default eigenschap.

In bovenstaand voorbeeld gaat het om een erg beperkt (met beperkte logica en mogelijkheden) collectietype gedefinieerd.  Veel meer logica kan nog worden opgenomen.  Zo is het soms nuttig bij het toevoegen van een element (hier Person object) na te gaan of de collectie niet reeds dat element bevat, enz... .
Het is ook aan te bevelen het "symmetrieprincipe" na te leven bij het  uitkiezen van de voor de clients beschikbare members.  Hiermee wordt in dit geval bedoeld dat als er een Add method aanwezig is, een Remove method  ook wel van nut zal zijn.  Maar dit blijft natuurlijk volledig naar de keuze van de auteur.

Verderop wordt behandeld welke voorgedefinieerde collectiestructuren men allemaal kan gebruiken, en hoe deze eventueel aan te passen.

9.8.3. Oefeningen

Opgave 1 :

Maak een klasse om voorstellingen te maken van rechthoeken.  Een rechthoek heeft als toestand een bepaalde hoogte en breedte (instelbaar en opvraagbaar).
Van rechthoeken moet men ook de oppervlakte kunnen bepalen.

Creëer ook een collectietype om een verzameling van rechthoeken te beheren.
De elementen in dergelijke collectie moet men via een index kunnen aanspreken.
Aan de collectie moet men rechthoeken kunnen toevoegen, en rechthoeken op een bepaalde index kunnen verwijderen.
De som van alle oppervlaktes van alle rechthoek elementen moet men kunnen opvragen van de collectie.

Schrijf zelf een client om bovenstaande klassen te testen.
Oplossing 1 :
Visual Basic 2012 Broncode - Codevoorbeeld 248
Class Rectangle
    Public Property Height As Integer
    Public Property Width As Integer
    Public Function GetArea() As Integer
        GetArea = Height * Width
    End Function
End Class
Class Rectangles
    Private _Items As Rectangle()
    Default Public ReadOnly Property Item(ByVal index As Integer) As Rectangle
        Get
            Item = _Items(index)
        End Get
    End Property
    Private _Count As Integer
    Public ReadOnly Property Count() As Integer
        Get
            Count = _Count
        End Get
    End Property
    Public Sub Add(ByVal rectangle As Rectangle)
        ReDim Preserve _Items(Count)
        _Items(Count) = rectangle
        _Count += 1
    End Sub
    Public Sub RemoveAt(ByVal index As Integer)
        For itemIndex As Integer = index To Count - 2
            _Items(itemIndex) = _Items(itemIndex + 1)
        Next
        ReDim Preserve _Items(Count - 2)
        _Count -= 1
    End Sub
    Public Function GetTotalArea() As Integer
        For Each item As Rectangle In _Items
            GetTotalArea += item.GetArea()
        Next
    End Function
End Class
Module Exercise1Solution
    Sub Main()
        Dim rectangle1 As Rectangle = New Rectangle
        rectangle1.Height = 1
        rectangle1.Width = 2
        Console.WriteLine(rectangle1.GetArea())
        '
        Dim rectangle2 As Rectangle = New Rectangle
        rectangle2.Height = 3
        rectangle2.Width = 4
        Console.WriteLine(rectangle2.GetArea())
        '
        Dim rectangles1 As Rectangles = New Rectangles
        rectangles1.Add(rectangle1)
        rectangles1.Add(rectangle2)
        '
        For index As Integer = 0 To rectangles1.Count - 1
            Console.Write(rectangles1(index).GetArea() & " ")
        Next
        Console.WriteLine()
        Console.WriteLine(rectangles1.GetTotalArea())
        '
        rectangles1.RemoveAt(1)
        '
        For index As Integer = 0 To rectangles1.Count - 1
            Console.Write(rectangles1(index).GetArea() & " ")
        Next
        Console.WriteLine()
        Console.WriteLine(rectangles1.GetTotalArea())
        '
        Console.ReadLine()
    End Sub
End Module
Console Application Output
2
12
2 12
14
2
2
Opgave 2 :
Maak een klasse waarvan objecten een voorstelling zijn van een zin.  Een zin is een ge-ordende lijst van woorden (Strings).

Het moet mogelijk zijn :
- een woord aan de zin toe te voegen (achteraan toevoegen)
- het aantal woorden van de zin op te vragen
- de positie (index) van een woord in de zin op te vragen
- te vragen of een woord zich al dan niet in de zin bevindt
- een woord op een bepaalde positie (index) op te vragen
Oplossing 2 :
Visual Basic 2012 Broncode - Codevoorbeeld 249
Module SentenceTestFixture
    Sub Main()
        Dim sentence1 As Sentence = New Sentence
        Console.WriteLine(sentence1.Count = 0)
        Console.WriteLine(sentence1.IndexOf("Hello") = -1)
        Console.WriteLine(sentence1.Contains("Hello") = False)
        '
        sentence1.Add("Hello")
        Console.WriteLine(sentence1.Count = 1)
        Console.WriteLine(sentence1.Item(0) = "Hello")
        Console.WriteLine(sentence1.IndexOf("Hello") = 0)
        Console.WriteLine(sentence1.Contains("Hello") = True)
        Console.WriteLine(sentence1.IndexOf("World") = -1)
        Console.WriteLine(sentence1.Contains("World") = False)
        '
        sentence1.Add("World")
        Console.WriteLine(sentence1.Count = 2)
        Console.WriteLine(sentence1.Item(0) = "Hello")
        Console.WriteLine(sentence1.Item(1) = "World")
        Console.WriteLine(sentence1.IndexOf("Hello") = 0)
        Console.WriteLine(sentence1.Contains("Hello") = True)
        Console.WriteLine(sentence1.IndexOf("World") = 1)
        Console.WriteLine(sentence1.Contains("World") = True)
        '
        Console.ReadLine()
    End Sub
End Module
Class Sentence
    Private _Words As String() = New String() {}
    Public ReadOnly Property Count() As Integer
        Get
            Count = _Words.Length
        End Get
    End Property
    Default Public ReadOnly Property Item(ByVal index As Integer) As String
        Get
            If index >= 0 AndAlso index < Count Then Item = _Words(index)
        End Get
    End Property
    Public Sub Add(ByVal word As String)
        ReDim Preserve _Words(Count)
        _Words(Count - 1) = word
    End Sub
    Public Function IndexOf(ByVal word As String) As Integer
        IndexOf = -1
        If Count > 0 Then
            Dim found As Boolean
            Do
                IndexOf += 1
                found = (Item(IndexOf) = word)
            Loop Until found OrElse IndexOf = Count - 1
            If Not found Then IndexOf = -1
        End If
    End Function
    Public Function Contains(ByVal word As String) As Boolean
        Contains = (IndexOf(word) <> -1)
    End Function
End Class
Console Application Output
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
True

9.8.4. Alternatieve Implementaties

Bovenstaande implementatie voor Sentence gebruikt intern een String array om de bevatte woorden in te beheren.  Dit is maar één mogelijkheid, er zijn echter tal van mogelijke implementaties.

Onderstaande variatie gaat bijvoorbeeld de zware ReDim Preserve operaties zo vaak mogelijk vermijden door gebruik te maken van een bepaalde capaciteit, die verdubbeld wordt indien deze te klein wordt :
Visual Basic 2012 Broncode - Codevoorbeeld 250
Namespace Alternative1
    Class Sentence
        Private _Count As Integer
        Private _Capacity As Integer = 16
        Private _Words(_Capacity - 1) As String
        Public ReadOnly Property Count() As Integer
            Get
                Count = _Count
            End Get
        End Property
        Default Public ReadOnly Property Item(ByVal index As Integer) As String
            Get
                Item = _Words(index)
            End Get
        End Property
        Public Sub Add(ByVal word As String)
            _Count += 1
            If Count > _Capacity Then
                _Capacity *= 2
                ReDim Preserve _Words(_Capacity - 1)
            End If
            _Words(Count - 1) = word
        End Sub
        Public Function IndexOf(ByVal word As String) As Integer
            IndexOf = -1
            If Count > 0 Then
                Dim found As Boolean
                Do
                    IndexOf += 1
                    found = (Item(IndexOf) = word)
                Loop Until found OrElse IndexOf = Count - 1
                If Not found Then IndexOf = -1
            End If
        End Function
        Public Function Contains(ByVal word As String) As Boolean
            Contains = (IndexOf(word) <> -1)
        End Function
    End Class
End Namespace
Voor meer details over het intelligent vergroten van arrays bekijk je best nog eens het topic over het dimensioneren van tabellen.

Onderstaande variatie maakt intern helemaal geen gebruik van een array, en is hier opgenomen om te illustreren hoe de implementatie totaal anders kan zijn zonder dat dit enige impact heeft voor - op zijn minst de code van - de client.

Er wordt in onderstaand voorbeeld gebruik gemaakt van een gelinkte lijst van woorden :
Visual Basic 2012 Broncode - Codevoorbeeld 251
Namespace Alternative2
    Class Sentence ' LinkedList Type
        Private _StartWord As Word = New Word With {.Value = "sentinel"}
        Public ReadOnly Property Count() As Integer
            Get
                Dim element As Word = _StartWord
                Do Until element.NextWord Is Nothing
                    element = element.NextWord
                    Count += 1
                Loop
            End Get
        End Property
        Default Public ReadOnly Property Item(ByVal index As Integer) As String
            Get
                If index >= 0 AndAlso index < Count Then
                    Dim elementIndex As Integer = -1
                    Dim element As Word = _StartWord
                    Do Until elementIndex = index OrElse element.NextWord Is Nothing
                        elementIndex += 1
                        element = element.NextWord
                    Loop
                    If elementIndex = index Then Item = element.Value
                End If
            End Get
        End Property
        Public Sub Add(ByVal word As String)
            Dim lastWord As Word = _StartWord
            Do Until lastWord.NextWord Is Nothing
                lastWord = lastWord.NextWord
            Loop
            lastWord.NextWord = New Word With {.Value = word}
        End Sub
        Public Function IndexOf(ByVal word As String) As Integer
            Dim elementIndex As Integer = -1
            Dim element As Word = _StartWord.NextWord
            Dim found As Boolean
            Do Until element Is Nothing OrElse found
                elementIndex += 1
                found = (element.Value = word)
                element = element.NextWord
            Loop
            If found Then
                IndexOf = elementIndex
            Else
                IndexOf = -1
            End If
        End Function
        Public Function Contains(ByVal word As String) As Boolean
            Contains = (IndexOf(word) <> -1)
        End Function
    End Class
    Class Word ' LinkedListNode Type
        Public Property Value As String
        Public Property NextWord As Word
    End Class
End Namespace
Bekijk het topic over LinkedList voor meer details over en andere voorbeelden van gelinkte lijsten.

Voor je de in bovenstaande oefening opgestelde testcode uit op deze implementatie van Sentence dan zie je hoe nog steeds alle testen slagen.
Wat illustreert dat de manier waarop klassen geïmplementeerd worden vrij kunnen variëren (eventueel in een latere fase), zonder dat de client zich daarvan bewust hoeft te zijn.
De client intresseert zich immers ook niet in hoe iets (een type) geïmplementeerd wordt.  Het enigste wat een gebruiker van het type Sentence intresseert is dat hij woorden kan toevoegen aan dergelijke zin, en naderhand bijvoorbeeld de positie van een woord in deze zin kan opvragen.
Hoe deze woorden worden bewaard, en wat er bijvoorbeeld juist gebeurt als de gebruiker een positie van een woord opvraagd, of over het algemeen : hoe het type zijn verantwoordelijkheden verwezelijkt, kan hem echter niet schelen.

Dit artikel is gepubliceerd op maandag 15 oktober 2012 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.