0

I have a class PageMaster, for which I instantiate several instances, including one called EmptyPage, which I never write to (only read from).

I have other class instances such as IndexPage, PrivacyPage et al. which have different properties set.

In my program (code below), when I modify any one of these class instances, every single other instance is also being modified in the exact same way.

Log3("Resetting special pages")

Log3("Setting pages equal to EmptyPage")
IndexPage = EmptyPage
PrivacyPage = EmptyPage
ContactPage = EmptyPage
AboutPage = EmptyPage
SiteIndexPage = EmptyPage
LogSpecialPages()

Log3("Setting flag IsIndexPage")
IndexPage.IsIndexPage = True
LogSpecialPages()

Log3("Setting flag isPrivacyPage")
PrivacyPage.IsPrivacyPage = True
LogSpecialPages()

Log3("Setting flag isContactPage")
ContactPage.IsContactPage = True
LogSpecialPages()

Log3("Setting flag isAboutPage")
AboutPage.IsAboutPage = True
LogSpecialPages()

Log3("Setting flag isSiteIndexPage")
SiteIndexPage.IsSiteIndexPage = True
LogSpecialPages()

Log3("Reset finished")

In this actual code from my program, when I perform the instruction IndexPage.IsIndexPage = True every single instance of Page - PrivacyPage, ContactPage, et al. are all modified - not just IndexPage. The same thing happens with the others. When I modify one instance, all other instances have string and/or boolean variable values changed.

(To do the logging, I serialize the class instances with JSONConvert and write it to an output.)

I can't understand why every single instance is being modified. This is how they are instantiated, inside my form's class:

Dim IndexPage As New PageMaster
Dim PrivacyPage As New PageMaster
Dim ContactPage As New PageMaster
Dim AboutPage As New PageMaster
Dim SiteIndexPage As New PageMaster
Dim EmptyPage As New PageMaster

The only possible explanation I can come up with is that when I say, IndexPage = EmptyPage, and assign all the others equal to EmptyPage, that they somehow lose their individual identities. That doesn't make sense to me, though, because I've never had anything like this happen.

The class is very simple, with a few string and boolean properties.

I then tried this simple modification. Instead of IndexPage = EmptyPage I used IndexPage = New PageMaster and it worked correctly.

This seems to confirm that, somehow, setting them all equal to EmptyPage is confusing their identifies.

Why do their identities become confused, causing any changes to one instance to be made to all?

It's like somehow when I say IndexPage = EmptyPage that it's just creating a symbolic reference from one to the other, rather than the behavior I expected, which was to set all the properties of one equal to the properties of the other.

UPDATE

After Jimi's comment confirming what I suspected to be the case, I decided to do a little test. I just put a button on my test app and added this code in its click event:

' PART 1:  STRING
Dim name1 As String = "Bob"
Dim name2 As String = "Larry"
name2 = name1
name2 = "Fred"
MsgBox(name1)

'PART 2: CUSTOM CLASS
Dim Bob As New Person
Bob.Name = "Bob"
Dim Larry As New Person
Larry.Name = "Larry"
Larry = Bob
Larry.Name = "Hubert"
MsgBox(Bob.Name)

In the first example, we get the response of "Bob", which is what I would have expected.

In the second msgbox, we don't get "Bob". Instead, we get "Hubert", because class instance Bob now points to Larry, and Larry's name was changed to "Hubert".

This prompts me to ask these questions:

  1. Why is the behavior is so completely different using String versus a class of our own creation?
  2. Can a different behavior be forced with custom classes? Or is there another simple way to "Copy" one instance of a class over another?
  3. Does this presumably carry over into C#, which I also use?
technonaut
  • 484
  • 3
  • 12
  • 1
    When you write this: `IndexPage = EmptyPage` you set the reference of `EmptyPage` to `IndexPage`, you're not creating a copy of `EmptyPage`. All other objects now point to the same instance of `EmptyPage`. So, when you assign `IndexPage.IsIndexPage = True`, you're actually setting the properties of `EmptyPage`, the instance to which all other objects point to. Simply don't do that. Just `Dim IndexPage As New PageMaster()`. If you need to add default values to the class properties, do it in PageMaster. – Jimi Dec 05 '20 at 20:32
  • 1
    Yes behaviour is similar in c# – Fabio Dec 06 '20 at 03:48

2 Answers2

1

Setting IndexPage = EmptyPage doesn't make a clone/copy of the EmptyPage object, it creates a reference to EmptyPage. (You could actually clone the EmptyPage object if you wish but I don't think that's what you're after here.)

Using IndexPage = New PageMaster instantiates a new PageMaster object called IndexPage, independent (except for any static properties, etc) from any other object instantiated from PageMaster, eg PrivacyPage.

This is really what Object Oriented Programming is about (among many other things).

If IndexPage, PrivacyPage, etc have exactly the same structure, instantiating them directly from PageMaster is fine. But if they have slightly - or even quite - different structures, you could create individual classes based on ("inheriting") PageMaster, or possiblly create an interface, eg IPageMaster. (You could even set the PageMaster class to be MustInherit so that it cannot be directly instantiated as an object.)

None of this is specific to VB.NET; the same basic principles apply to any OOP language including C#.NET.

As an aside, be careful if you define any static properties (Shared in VB) within your base class; static variables are common to all objects of the class, so their values will change even in derived class objects.

I've glossed over quite a bit here, but hopefully you get the basic idea.

SteveCinq
  • 1,920
  • 1
  • 17
  • 22
  • Great thought about inheritance - it made me re-think the whole approach. I re-coded everything and it's so much more elegant now, and less code, too. – technonaut Dec 06 '20 at 18:43
  • @technonaut31337 `Class Collie Inherits Class Dog Inherits Class Animal ...` So, so much clearer and maintainable, yes? – SteveCinq Dec 06 '20 at 18:49
1

With regards to your update:

Why is the behavior is so completely different using String versus a class of our own creation?

It's not. It's exactly the same. The only difference between String and other types is that String supports literals. In this code:

Dim name1 As String = "Bob"
Dim name2 As String = "Larry"
name2 = name1
name2 = "Fred"
MsgBox(name1)

The fourth line is not changing something about the String object that name2 refers to. It is assigning a different String object to name2. Why would you expect that to affect the name1 variable or the String object that it refers to? As for this code:

Dim Bob As New Person
Bob.Name = "Bob"
Dim Larry As New Person
Larry.Name = "Larry"
Larry = Bob
Larry.Name = "Hubert"
MsgBox(Bob.Name)

you haven't really thought through what it actually does. After the first four lines, Bob and Larry refer to two different Person objects. The fifth line discards the object that Larry refers to and assigns the same Person object to Larry as Bob already refers to. Both variables now refer to the same Person object. If you make a change to the object via one variable, of course you're going to see that change via the other variable. How could you not?

Consider this real-life scenario. Let's say that I am best friends with a person named Bob and you are best friends with a person named Larry. You have an argument with Larry and are no longer friends and you become best friends with Bob. You and I are now both best friends with the same person. If your best friend then changed his name to Hubert, what would my best friend's name be? It would be Hubert, right? That's blindingly obvious, so why is it a problem that that is how it works in OOP? Programming objects are designed to mimic real-world objects, which is why things work the same in OOP as in the real world.

One thing that can also cause some confusion is the fact that String objects are immutable, i.e. once created, they cannot be changed. This means that, while you are able to change some aspect of a Person object in your second code snippet, there's no way to make a change to a String object in the first. If you have a variable of type String then you can assign a different String object to it - either a literal or the result of an expression - but you cannot modify the object it refers to.

Can a different behavior be forced with custom classes? Or is there another simple way to "Copy" one instance of a class over another?

There is only one behaviour for reference types. ALL reference types work exactly the same way. If you want to create a copy of a reference-type object, i.e. an instance of a class, then you have to create it. Classes that require this usually implement the ICloneable interface, but you still need to provide the implementation yourself. That said, it is not uncommon for people to think that they need such functionality when they don't.

Does this presumably carry over into C#, which I also use?

Of course. OOP is OOP.

jmcilhinney
  • 50,448
  • 5
  • 26
  • 46