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

21.6. Cloning - ICloneable Interface

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 zou willen werken met kopies (of ook wel klonen genoemd) van objecten, waarbij men dan kan werken met de kopie/kloon die initieel dezelfde toestand heeft als het originele object.

Veronderstel onderstaande klasse Division.
Visual Basic 2012 Broncode - Codevoorbeeld 476
Namespace Example1
    Class Division
        Public Sub New(ByVal dividend As Double, ByVal divisor As Double)
            Me.Dividend = dividend
            Me.Divisor = divisor
        End Sub
        Public Property Dividend As Double
        Public Property Divisor As Double
        Public ReadOnly Property Quotient() As Decimal
            Get
                Quotient = Convert.ToDecimal(Dividend / Divisor)
            End Get
        End Property
        Public Overrides Function ToString() As String
            ToString = Dividend.ToString() & " / " & Divisor.ToString() & _
                       " = " & Quotient.ToString()
        End Function
    End Class
End Namespace
Om in een client een kloon te bekomen van een bepaald Division object, kunnen we (indien de constructor dit toelaat) de toestand van het te klonen object doorgeven aan de constructoren van de kloon.
Visual Basic 2012 Broncode - Codevoorbeeld 477
Namespace Example1
    Class Client1
        Public Shared Sub Main()
            Dim division1 As Division = New Division(10, 5)
            Dim division1Clone As Division = _
                             New Division(division1.Dividend, division1.Divisor)
            '
            Console.WriteLine(division1.ToString())
            Console.WriteLine(division1Clone.ToString())
            '
            division1Clone.Dividend = 20
            '
            Console.WriteLine(division1.ToString())
            Console.WriteLine(division1Clone.ToString())
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
10 / 5 = 2
10 / 5 = 2
10 / 5 = 2
20 / 5 = 4
Deze werkwijze slaagt zolang we over een constructor beschikken die dit toelaat.  Vaak is het echter zo dat niet de volledige toestand kan worden geïnjecteerd in een object, dan wordt het moeilijker.

Bemerk dat een wijziging aan de kloon, niets zal wijzigen aan het origineel ( en omgekeerd).

Het blijft hoe dan ook zo dat de client hier het werk moet verrichten om aan een kloon van het originele object te bekomen.

We kunnen de verantwoordelijkheid voor het kunnen maken van een kloon ook verschuiven naar het te klonen datatype zelf.  Dus hier bijvoorbeeld ervoor zorgen dat het type Division zelf een kloon kan opleveren.  Division is immers de "information expert" van deze verantwoordelijkheid.

"Information expert" is een pattern dat stelt dat je een verantwoordelijkheid toekent aan dat type dat beschikt over voldoende informatie (het "information expert" type) om die verantwoordelijkheid te vervullen.  Een Division object beschikt hier over alle informatie (de Dividend en Divisor) om een kloon te creëren.

21.6.1. ICloneable Interface

In datatypes waarvan klonen van instanties worden gemaakt, implementeert men vaak de ICloneable interface.  Dit is niet noodzakelijk, maar het maakt objecten van dat datatype meteen in een ICloneable-context bruikbaar.

Iets wat ICloneable is, beschikt over een Function Clone() As Object method, die in zen implementatie een kloon oplevert van het object waarvan de kloon wordt opgevraagd.

We kunnen de Division klasse als volgt gaan uitbreiden :
Visual Basic 2012 Broncode - Codevoorbeeld 478
Namespace Example1
    Partial Class Division : Implements ICloneable
        Private Function getClone() As Object Implements System.ICloneable.Clone
            getClone = New Division(Dividend, Divisor)
        End Function
        Public Function Clone() As Division
            Clone = DirectCast(getClone(), Division)
        End Function
    End Class
End Namespace
"Private-implementation" werd hier toegepast om te vermijden dat de publieke interface van Division zou beschikken over een niet strongly typed getClone() member.  Een kloon van een Division object zal immers nooit een Object, maar altijd een Division zijn.  Een strongly typed Clone() method werd hier dan wel aan de publieke interface van Division toegevoegd.
Visual Basic 2012 Broncode - Codevoorbeeld 479
Namespace Example1
    Partial Class Client2
        Public Shared Sub Main()
            Dim division1 As Division = New Division(10, 5)
            Dim division1Clone As Division = division1.Clone()
            '
            Console.WriteLine(division1.ToString())
            Console.WriteLine(division1Clone.ToString())
            '
            division1Clone.Dividend = 20
            '
            Console.WriteLine(division1.ToString())
            Console.WriteLine(division1Clone.ToString())
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
10 / 5 = 2
10 / 5 = 2
10 / 5 = 2
20 / 5 = 4
Het werk dat de client moet verrichten om een kloon te bekomen is sterk (en maximaal) vereenvoudigd.

Tal van voorgedefinieerde types uit de klassenbibliotheek van het .NET Framework zijn reeds ICloneable, enkele voorbeelden : String, Array, ArrayList, SortedList, Queue, Stack, HashTable, ...

21.6.2. MemberwiseClone

Een alternatief voor het gebruik van de constructor om een kopie/kloon te creëren, is het gebruik van de Protected Function MemberwiseClone() As Object method die wordt overgeërfd uit de Object klasse (1).

Deze method is ingekapseld (Protected) en kan dus niet door clients, maar enkel intern worden gebruikt.

De MemberwiseClone() method zal in Object vorm een kloon opleveren, waarbij de volledige toestand werd gekopieerd.  Alle veldwaarden van het originele object worden overgenomen in de kloon.
Visual Basic 2012 Broncode - Codevoorbeeld 480
Namespace Example2
    Class Division : Implements ICloneable
        Public Sub New(ByVal dividend As Double, ByVal divisor As Double)
            Me.Dividend = dividend
            Me.Divisor = divisor
        End Sub
        Public Property Dividend As Double
        Public Property Divisor As Double
        Public ReadOnly Property Quotient() As Decimal
            Get
                Quotient = Convert.ToDecimal(Dividend / Divisor)
            End Get
        End Property
        Public Overrides Function ToString() As String
            ToString = Dividend.ToString() & " / " & Divisor.ToString() & _
                       " = " & Quotient.ToString()
        End Function
        Private Function getClone() As Object Implements System.ICloneable.Clone
            getClone = MemberwiseClone()                                   ' (1)
        End Function
        Public Function Clone() As Division
            Clone = DirectCast(getClone(), Division)
        End Function
    End Class
End Namespace
In onderstaand client zal de Clone() implementatie een nieuw Divison object creëren en meteen alle velden (Dividend en Divisor) initialiseren op een kopie van de veldwaarden van het originele object.
Visual Basic 2012 Broncode - Codevoorbeeld 481
Namespace Example2
    Class Client
        Public Shared Sub Main()
            Dim division1 As Division = New Division(10, 5)
            '
            Dim division1Clone As Division = division1.Clone()
            '
            Console.WriteLine(division1.ToString())
            Console.WriteLine(division1Clone.ToString())
            '
            division1Clone.Dividend = 20
            '
            Console.WriteLine(division1.ToString())
            Console.WriteLine(division1Clone.ToString())
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
10 / 5 = 2
10 / 5 = 2
10 / 5 = 2
20 / 5 = 4
Het gebruik van de MemberwiseClone() method is een ook goed alternatief voor situaties waar de constructor niet zou toelaten alle veldwaarden te injecteren in het nieuwe object.

In onderstaand voorbeeld werd Person ICloneable gemaakt om clients toe te laten klonen te creëren van Person objecten.
Visual Basic 2012 Broncode - Codevoorbeeld 482
Namespace Example3
    Class Address
        Public Sub New(ByVal street As String)
            Me.Street = street
        End Sub
        Public Property Street As String
        Public Overrides Function ToString() As String
            ToString = Street
        End Function
        ' ...
    End Class
    Class Person : Implements ICloneable
        Public Sub New(ByVal name As String, ByVal address As Address)
            Me.Name = name
            Me.Address = address
        End Sub
        Public Property Name As String
        Public Property Address As Address
        Public Overrides Function ToString() As String
            ToString = Name
            If Address IsNot Nothing Then ToString &= " - " & Address.ToString()
        End Function
        ' ...
        Private Function getClone() As Object Implements System.ICloneable.Clone
            getClone = MemberwiseClone()
        End Function
        Public Function Clone() As Person
            Clone = DirectCast(getClone(), Person)
        End Function
    End Class
    Class Client1
        Public Shared Sub Main()
            Dim addressPerson1 As Address = New Address("8th Street")
            Dim person1 As Person = New Person("John", addressPerson1)
            Dim person1Clone As Person
            '
            person1Clone = person1.Clone()
            '
            Console.WriteLine(person1.ToString())
            Console.WriteLine(person1Clone.ToString())
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
John - 8th Street
John - 8th Street
Bovenstaande Client lijkt het juist gedrag te vertonen.  Maar als we een wijziging zouden aanbrengen aan het adres van person1 of zijn kloon ( person1Clone) dan zie je dat beide Person objecten hetzelfde Address object delen.
Visual Basic 2012 Broncode - Codevoorbeeld 483
Namespace Example3
    Partial Class Client2
        Public Shared Sub Main()
            Dim addressPerson1 As Address = New Address("8th Street")
            Dim person1 As Person = New Person("John", addressPerson1)
            Dim person1Clone As Person = person1.Clone()
            '
            Console.WriteLine(person1.ToString())
            Console.WriteLine(person1Clone.ToString())
            '
            addressPerson1.Street = "9th Street"
            '
            Console.WriteLine(person1.ToString())
            Console.WriteLine(person1Clone.ToString())
        End Sub
    End Class
End Namespace
Console Application Output
John - 8th Street
John - 8th Street
John - 9th Street
John - 9th Street
De kloon die we de clients toelaten te maken is dus een "shallow clone/copy" ( "ondiepe kloon/kopie").  Bij het klonen van person1 is een simpele kopie van de referentie van addressPerson1 toegekend aan Address van person1Clone.  Beide Person objecten werken dus met hetzelfde Address object.

Dit is een typisch probleem dat geldt voor het maken van klonen van een container die referencetype velden bevat.  Bij het maken van een "shallow copy" (zoals bij het gebruik van de MemberwiseClone() method) wordt immers steeds slechts de inhoud van de velden gekopieerd.  De inhoud van een referencetype variabele is steeds de referentie naar het object.  Dat in tegenstelling tot het kopiëren van een valuetype waarde.  Elke valuetype variabele/veld is ge-associeerd met een eigen valuetype instantie.  Bij het kopiëren van de inhoud van een valuetype variabele/veld naar een andere valuetype variabele/veld zal dus een kopie van de volledige instantie aan de toegekende valuetype variabele/veld worden ge-associeerd.
Method MemberwiseClone() bijvoorbeeld zal dus van valuetype velden wel een "deep clone/copy" ("diepe kloon/kopie") maken.

21.6.3. Deep Clone

Om toch een "deep clone/copy" van een Person object te kunnen maken, waarbij de kloon van het Person object ook een kloon van het Address object zal bevatten, kunnen we ook het type Address ICloneable maken (1).

Bij het klonen van een Person object moeten dan weer manueel een nieuw Person object creëren, waarbij we aan de constructor van dat nieuw object een kloon van het originele Address object doorgeven (2).
Visual Basic 2012 Broncode - Codevoorbeeld 484
Namespace Example4
    Class Address : Implements ICloneable                                  ' (1)
        Public Sub New(ByVal street As String)
            Me.Street = street
        End Sub
        Public Property Street As String
        Public Overrides Function ToString() As String
            ToString = Street
        End Function
        ' ...
        Private Function getClone() As Object Implements System.ICloneable.Clone
            getClone = MemberwiseClone()
        End Function
        Public Function Clone() As Address
            Clone = DirectCast(getClone(), Address)
        End Function
    End Class
    Class Person : Implements ICloneable
        Public Sub New(ByVal name As String, ByVal address As Address)
            Me.Name = name
            Me.Address = address
        End Sub
        Public Property Name As String
        Public Property Address As Address
        Public Overrides Function ToString() As String
            ToString = Name
            If Address IsNot Nothing Then ToString &= " - " & Address.ToString()
        End Function
        ' ...
        Private Function getClone() As Object Implements System.ICloneable.Clone
            getClone = New Person(Name, Address.Clone())                   ' (2)
        End Function
        Public Function Clone() As Person
            Clone = DirectCast(getClone(), Person)
        End Function
    End Class
    Class Client
        Public Shared Sub Main()
            Dim namePerson1 As String = "John"
            Dim addressPerson1 As Address = New Address("8th Street")
            Dim person1 As Person = New Person(namePerson1, addressPerson1)
            Dim person1Clone As Person = person1.Clone()
            '
            Console.WriteLine(person1.ToString())
            Console.WriteLine(person1Clone.ToString())
            '
            person1Clone.Name = "Jane"                                        ' (3)
            person1Clone.Address.Street = "9th Street"
            '
            Console.WriteLine(person1.ToString())
            Console.WriteLine(person1Clone.ToString())
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
John - 8th Street
John - 8th Street
John - 8th Street
John - 9th Street
Bemerk dat we bij het creëren van de "deep clone" van een Person object geen kloon aan de constructor hoeven door te geven van de Name eigenschap.  Deze Name eigenschap is nochtans van het type String, wat ook een referencetype is.
Het String datatype is "immutable", een wijziging aan de Name van de kloon (zoals op regel (3)) zal immers een nieuw String object ("Jane") creëren, en de referentie van dat nieuwe String object toekennen aan de Name eigenschap van de kloon ( person1Clone).  Het originele object person1 zal echter nog altijd dezelfde referentie naar het originele String object ("John") bevatten.
We hoeven ons dus niet bezig te houden met het expliciet creëren van klonen van String velden, wat een voordeel is veroorzaakt door de "immutable" karakteristiek van het String datatype.

Zoals je uit vorig voorbeeld kunt merken kan het vervelend zijn een "deep clone/copy" te creëren van een object van een containing type.  Zeker indien de contained objecten zelf van een containing type zijn.

Containment wordt zo vaak gebruikt, dat het creëren van een "deep copy" vaak het kopiëren inhoud van een volledige "objectgraph".

21.6.4. Klonen via Serialisatie

Een eenvoudig alternatief hiervoor is het gebruiken van "serialisatie".  Het is hier niet de bedoeling serialisatie te bespreken, maar voor het maken van "deep copies" van volledige "objectgraphs" kan het zo handig zijn dat we dit hier toch nog even gaan illustreren.

We marken het datatype van de objecten die gekloond worden met het Serializable() attribuut.  We serialiseren het rootobject naar een memorystream om het vervolgens weer uit deze stream te deserialiseren.  En het is bij het deserialiseren dat onze nieuwe objecten (onze klonen) worden gemaakt :
Visual Basic 2012 Broncode - Codevoorbeeld 485
Namespace Example5
    <Serializable()> Class Address
        Public Sub New(ByVal street As String)
            Me.Street = street
        End Sub
        Public Property Street As String
        Public Overrides Function ToString() As String
            ToString = Street
        End Function
        ' ...
    End Class
    <Serializable()> Class Person : Implements ICloneable
        Public Sub New(ByVal name As String, ByVal address As Address)
            Me.Name = name
            Me.Address = address
        End Sub
        Public Property Name As String
        Public Property Address As Address
        Public Overrides Function ToString() As String
            ToString = Name
            If Address IsNot Nothing Then ToString &= " - " & Address.ToString()
        End Function
        ' ...
        Private Function getClone() As Object Implements System.ICloneable.Clone
            Dim stream As System.IO.MemoryStream = New System.IO.MemoryStream
            Dim formatter As  _
              System.Runtime.Serialization.Formatters.Binary.BinaryFormatter = _
              New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            formatter.Serialize(stream, Me)
            stream.Seek(0, System.IO.SeekOrigin.Begin)
            getClone = formatter.Deserialize(stream)
            stream.Close()
        End Function
        Public Function Clone() As Person
            Clone = DirectCast(getClone(), Person)
        End Function
    End Class
    Class Client
        Public Shared Sub Main()
            Dim namePerson1 As String = "John"
            Dim addressPerson1 As Address = New Address("8th Street")
            Dim person1 As Person = New Person(namePerson1, addressPerson1)
            Dim person1Clone As Person = person1.Clone()
            '
            Console.WriteLine(person1.ToString())
            Console.WriteLine(person1Clone.ToString())
            '
            person1Clone.Name = "Jane"
            person1Clone.Address.Street = "9th Street"
            '
            Console.WriteLine(person1.ToString())
            Console.WriteLine(person1Clone.ToString())
            '
            Console.ReadLine()
        End Sub
    End Class
End Namespace
Console Application Output
John - 8th Street
John - 8th Street
John - 8th Street
John - 9th Street
Bemerk dat ondanks het type Address niet ICloneable is, en ondanks dat niet expliciet een kloon wordt gemaakt van het containde Address object, er toch met een kloon/kopie van dat Address object wordt gewerkt in het person1Clone object.

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