Why Copying "Reference Types" in VB.NET Causes Glitches

106 10


In VB.NET, 'types' (the catch-all name for a piece of information in the .NET Framework) are either 'value' types or 'reference' types. (This is consistent with the ByVal or ByRef keywords because it indicates whether a copy is made of the piece of information or whether two pointers to the same location in memory are made when the piece of information is 'copied'. In Matthew McDonald's The Book of VB.NET: .NET Insight for VB Developers, he writes:

"Setting two arrays equal to each other doesn’t copy the contents of an array, only the reference. You end up with two array variables that access the same array."

I cover 'value' types and 'reference' types in the short article Nullable Types Give Visual Basic the Ability to Say Nothing.

Let's look at how this can lead to really subtle bugs if you don't understand the difference in more detail.

An integer is a value type. So if you code ...

Dim MyIntegerA As IntegerDim MyIntegerB As IntegerMyIntegerB = 10MyIntegerA = MyIntegerBMyIntegerB = 20MessageBox.Show("MyIntegerA is " & MyIntegerA & vbCrLf & "MyIntegerB is " & MyIntegerB)
... you get what is shown in the illustration below:

--------
Click Here to display the illustration
--------


MyIntegerA remains 10 and what you might do to a copy of it doesn't affect the value.

Arrays in .NET don't follow the same rules, however. Because assigning one array to another simply makes a copy of the reference to the same array in memory, when you change the value for one, you change it for the other as well.

So in the following code, the value of 10 saved in the third (index number 2 in VB.NET) element of an array gets overwritten when one array is assigned to another.

Dim MyIntegerA(5) As IntegerDim MyIntegerB(5) As IntegerMyIntegerB(2) = 10MyIntegerA = MyIntegerBMyIntegerB(2) = 20MessageBox.Show("MyIntegerA(2) is " & MyIntegerA(2) & vbCrLf & "MyIntegerB(2) is " & MyIntegerB(2))
--------
Click Here to display the illustration
--------


If your program assumed that the value of MyIntegerA(2) would stay the same, you would have a nasty bug to find!

You can make a copy of your array so you don't have this problem by using the Clone() method of the array object:

MyIntegerA = MyIntegerB.Clone()
A duplicate copy of MyIntegerB goes into MyIntegerA and then there are two copies of the array.

The Clone method is a great example of how .NET itself uses an Interface to work it's magic. Clone is fundamentally a method (actually, the only method) of the System.ICloneable interface. According to Microsoft, Clone is "intended to support cloning beyond that supplied by MemberwiseClone." (Don't you just love it when they explain something in terms that you also don't understand?) As explained in my article Interface Definitions and Why You Should Care:

"Interfaces define the properties, methods, and events that classes can implement. ... Interfaces cannot contain any implementation code or statements associated with implementation code."

So, there are dozens and dozens of different implementations of Clone. Don't make the mistake of thinking that they're all the same. In particular, Clone can be implemented either as a deep copy or a shallow copy. MemberwiseClone creates a shallow copy. My article Shallow Copy versus Deep Copy explains what a deep copy and a shallow copy are.

One of these implementation of Clone is String.Clone and it's not obvious at all what it does. Microsoft notes (in all of the versions of .NET, so you can see it over and over) that when you use String.Clone, "The return value is not an independent copy of this instance; it is simply another view of the same data."

To see what it does, let's compare it with the shared function of the String object, Copy. First create three strings ...

Dim theString As New String("AAAAA")Dim theClonedString As New String("AAAAA")Dim theCopiedString As New String("AAAAA")
Remembering that the Is operator returns True if two object references refer to the same object in memory and False otherwise, let's see what the shared Copy method does. Shared methods are covered in "Static" (that is, "Shared") versus Dynamic Types in VB.NET.

Console.WriteLine("Using String.Copy")theString = String.Copy(theCopiedString)Console.WriteLine(theString = theCopiedString)Console.WriteLine(theString Is theCopiedString)' Returns ....' True' False
Is returns False because the shared Copy function creates a whole new object.

Now, let's see what happens when the Clone method is used.

Console.WriteLine("Using string.Clone")theString = theClonedString.CloneConsole.WriteLine(theString = theClonedString)Console.WriteLine(theString Is theClonedString)' Returns ....' True' True
Clone just creates another pointer to the same area in memory, so of course the values are equal as well.

You can find quite a few forums where someone asks, "What's the difference between the shared function of String "Copy" and the interface method String "Clone" (Other than the Is operator.) It's difficult to find a situation where they actually do anything different and forums typically answer that question by repeating the Microsoft documentation, "One makes a complete new copy of a string and the other just creates a new pointer." The reason is that there's action taking place behind the curtain that, as far as I can see, Microsoft never tells you about. To see that action, let's simply assign a value to theClonedString and then repeat the code above.

Console.WriteLine("Assigning a Value makes a new instance of String")theClonedString = "AAAAA"Console.WriteLine(theString = theClonedString)Console.WriteLine(theString Is theClonedString)' Returns ....' True' False
Whenever you actually do anything with the cloned copy, .NET makes a totally new object anyway. This doesn't follow the example in the first part of the article. So in actual practice, the difference just isn't there.

The things Microsoft doesn't tell you!
Source...

Leave A Reply

Your email address will not be published.