Stavros
Stavros

Reputation: 23

Object Property Being Changed When It's Not Supposed To? (Vb.net)

I'll try to keep it simple, but this is making me almost rip my hair out as I do not understand why a certain property is being changed on one of my objects when I am not assigning it a change.

Sample class:

Public Class Person
Public Name As String
Public Age as UInteger
End Class

OK cool. In my project:

Dim Me as New Person, You as New Person
Me.Name = "John"
You.Name = "Terry"
Me = You
You.Age = 32

So I assigned the Name properties to 'Me' and 'You' respectively as John & Terry. I then coped the properties of 'You' To 'Me'.

When I go change the Age Property of 'You' to 32. The property age of 'Me' ALSO gets changed to 32 even though I never assigned it a change.

A total head scratcher as I am not quite catching the error here?

Upvotes: 2

Views: 533

Answers (1)

jmcilhinney
jmcilhinney

Reputation: 54487

If you want to create a new object with the same property values then you have no option but to create a new object and copy the property values. There are a number of ways that you can implement the details though.

The most obvious and most basic is to simply create a new object and copy the property values:

Public Class Person
    Public Property Name As String
    Public Property Age As UInteger
End Class
Dim firstPerson As New Person

firstPerson.Name = "John"
firstPerson.Age = 32

Dim secondPerson As New Person

secondPerson.Name = firstPerson.Name
secondPerson.Age = firstPerson.Age

The next option to consider is to build the functionality into the type itself. If you do this, the first option is to implement the ICloneable interface and use the MemberwiseClone method:

Public Class Person
    Implements ICloneable

    Public Property Name As String
    Public Property Age As UInteger

    Public Function Clone() As Object Implements ICloneable.Clone
        Return MemberwiseClone()
    End Function

End Class
Dim firstPerson As New Person

firstPerson.Name = "John"
firstPerson.Age = 32

Dim secondPerson = DirectCast(firstPerson.Clone(), Person)

The ICloneable.Clone method returns an Object reference, because it must work for any type, so it's not ideal. You might choose not to implement the interface and change the return type, or you could add your own Copy method and cast the result:

Public Class Person
    Implements ICloneable

    Public Property Name As String
    Public Property Age As UInteger

    Public Function Clone() As Object Implements ICloneable.Clone
        Return MemberwiseClone()
    End Function

    Public Function Copy() As Person
        Return DirectCast(Clone(), Person)
    End Function

End Class
Dim firstPerson As New Person

firstPerson.Name = "John"
firstPerson.Age = 32

Dim secondPerson = firstPerson.Copy()

That MemberwiseClone method creates a shallow copy of the current object, which means a direct copy of property value. That's fine for simple types like String and numeric types but may not be for more complex types. For instance, if you had a property that referred to a DataSet then the same property in the new object would refer to that same DataSet object, not a new DataSet containing the same data. The same goes for arrays and collections. If you don't want a shallow copy then you need to write your own code to explicitly create a deep copy in the specific way that you want, e.g.

Public Class Person

    Public Property Name As String
    Public Property Age As UInteger
    Public ReadOnly Property Children As New List(Of Person)

    Public Function Copy() As Person
        Dim newPerson As New Person With {.Name = Name,
                                          .Age = Age}

        For Each child As Person In Children
            newPerson.Children.Add(child.Copy())
        Next

        Return newPerson
    End Function

End Class
Dim firstPerson As New Person

firstPerson.Name = "John"
firstPerson.Age = 32
firstPerson.Children.Add(New Person With {.Name = "Jim", .Age = 10})
firstPerson.Children.Add(New Person With {.Name = "Jane", .Age = 12})

Dim secondPerson = firstPerson.Copy()

The reason that's not done by default is that you could end up with a stack overflow. In this case, creating a copy of a Person object will also create a copy of the Person objects in its Children collection. If there is a circular reference there somewhere, i.e. two Person objects were in each other's Children collection then you would just keep creating copies until the system ran out of resources. You need to be very careful when creating deep copies in order to avoid such situations. You need to be very sure that you only go to the depth you need and that you catch any circular references and stop copying when you get back to the start of the chain.

Notice that this last example does require you to specify each property explicitly. It is possible to avoid this if you use Reflection but that is less efficient and, unless you have a ridiculous number of properties, a bit silly as the few minutes it takes to type out those properties should be a very minor annoyance.

Upvotes: 3

Related Questions