Search code examples
.netvb.netdeep-copyicloneable

ICloneable deepcopy of an object in .net


I'm trying to add a deep-copy method using ICloneable to classes that have been auto-generated from an xsd using xsd.exe. I can get it to work on a simple level but as soon as the objects become nested then the clone method doesn't work.

I'm pretty sure i've got the clone method wrong on the DirectorReturnType class but I can't work out how to fix it.

Can anyone offer any assistance? I've attached the subs and classes below:

        Dim oDirRetType As New DirectorReturnType
        Dim oDirPerType As New DirectorPersonType

        Dim DirPerTypeT1 As New DirectorPersonType
        Dim DirPerTypeT2 As New DirectorPersonType

        Dim DirRetTypeT1 As New DirectorReturnType
        Dim DirRetTypeT2 As New DirectorReturnType

        Dim AROT1 As New AnnualReturnOfficer
        Dim AROT2 As New AnnualReturnOfficer

This works as expected and messages "test1" and then "test2":

        'Works
        oDirPerType.Occupation = "test1"
        DirRetTypeT1.Item = oDirPerType.Clone

        oDirPerType.Occupation = "test2"
        DirRetTypeT2.Item = oDirPerType.Clone

        DirPerTypeT1 = DirRetTypeT1.Item
        DirPerTypeT2 = DirRetTypeT2.Item

        MsgBox(DirPerTypeT1.Occupation)
        MsgBox(DirPerTypeT2.Occupation)

If I then add an extra object AROTx of type AnnualRetunOfficer then it messages "Test2" then "Test2".

        'Doesnt Work
        oDirPerType.Occupation = "test1"
        oDirRetType.Item = oDirPerType
        AROT1.Item = oDirRetType.Clone

        oDirPerType.Occupation = "test2"
        DirRetTypeT2.Item = oDirPerType
        AROT2.Item = oDirRetType.Clone

        DirRetTypeT1 = AROT1.Item
        DirPerTypeT1 = DirRetTypeT1.Item

        DirRetTypeT2 = AROT2.Item
        DirPerTypeT2 = DirRetTypeT2.Item

        MsgBox(DirPerTypeT1.Occupation)
        MsgBox(DirPerTypeT2.Occupation)

DirectorPersonType:

Partial Public Class DirectorPersonType

Inherits PersonBaseType

Implements ICloneable

Private serviceAddressField As ServiceAddressType

Private dOBField As Date

Private nationalityField As String

Private occupationField As String

Private countryOfResidenceField As String

Private previousNamesField() As PreviousNameType

'''<remarks/>
Public Property ServiceAddress() As ServiceAddressType
    Get
        Return Me.serviceAddressField
    End Get
    Set(value As ServiceAddressType)
        Me.serviceAddressField = value
    End Set
End Property

'''<remarks/>
<System.Xml.Serialization.XmlElementAttribute(DataType:="date")> _
Public Property DOB() As Date
    Get
        Return Me.dOBField
    End Get
    Set(value As Date)
        Me.dOBField = value
    End Set
End Property

'''<remarks/>
Public Property Nationality() As String
    Get
        Return Me.nationalityField
    End Get
    Set(value As String)
        Me.nationalityField = value
    End Set
End Property

'''<remarks/>
Public Property Occupation() As String
    Get
        Return Me.occupationField
    End Get
    Set(value As String)
        Me.occupationField = value
    End Set
End Property

'''<remarks/>
Public Property CountryOfResidence() As String
    Get
        Return Me.countryOfResidenceField
    End Get
    Set(value As String)
        Me.countryOfResidenceField = value
    End Set
End Property

'''<remarks/>
<System.Xml.Serialization.XmlElementAttribute("PreviousNames")> _
Public Property PreviousNames() As PreviousNameType()
    Get
        Return Me.previousNamesField
    End Get
    Set(value As PreviousNameType())
        Me.previousNamesField = value
    End Set
End Property

Public Function Clone() As Object Implements System.ICloneable.Clone

    Return New DirectorPersonType With {.CountryOfResidence = CountryOfResidence, .DOB = DOB, .Forename = Forename, .Nationality = Nationality, .Occupation = Occupation, .OtherForenames = OtherForenames, .PreviousNames = PreviousNames, .ServiceAddress = ServiceAddress, .Surname = Surname, .Title = Title}

End Function

End Class

DirectorReturnType:

Partial Public Class DirectorReturnType

Implements ICloneable

Private itemField As Object

'''<remarks/>
<System.Xml.Serialization.XmlElementAttribute("Corporate",
GetType(CorporateOfficerType)), _
System.Xml.Serialization.XmlElementAttribute("Person", 
GetType(DirectorPersonType))> _
Public Property Item() As Object
    Get
        Return Me.itemField
    End Get
    Set(value As Object)
        Me.itemField = Value
    End Set
End Property

Public Function Clone() As Object Implements System.ICloneable.Clone

    Return New DirectorReturnType With {.Item = Item}

End Function

Solution

  • Your Clone is not actually making a fresh copy of a few variables, instead it is pointing to existing values stored in your original class.

    Namely these two variables:

    Private serviceAddressField As ServiceAddressType
    Private previousNamesField() As PreviousNameType
    

    Will need special attention in your Clone function.

    It is tidy (perhaps not perfectly standard) to call ME.MemberwiseClone to get a new shallow copy of your instance. Then you MUST deal with your non standard types (Arrays, classes) separately and create fresh copies of them.

    Something like (please correct any bugs, but this is the general idea)

    Public Function Clone() As Object Implements ICloneable.Clone
       Dim typClone As DirectorPersonType = Me.MemberwiseClone ' Shallow clone taken and new object created
       Try
          typClone.serviceAddressField = Me.serviceAddressField.Clone ' create a new copy with same value
    
          typClone.previousNamesField.AddRange(Me.previousNamesField) ' create a new copy with the same values
          ' PreviousNamesField will need some work generating a new array
          ' let me know if you need more detail or a working example
    
          ' However, I have been corrected and will share that this method is 
          ' definitely superior to AddRange, using LINQ to
          ' create a new list with a copy of the items
           typClone.ListName = Me.ListName.Select(Function(x) x.Clone()).Cast(Of ClassName).ToList
    
       Catch ex As Exception
             ' catch errors and handle here!
       End Try
       Return typClone
    End Function