Object References – a Subtle Trap
Reference variables – variables that refer to objects rather than values – are at the heart of VB.Net programming. Most of the implications of using reference variables can be ignored most of the time, but there are a few occasions when understanding how reference variables work is critical. One of these involves lists and loops.
Objects come and go. They are created and they die. Their creation is often explicit and easily identifiable – their death is often quiet and automatic. But there are exceptions. For instance, new string objects are created much more frequently than you might expect. And some objects seem to refuse to die. Issues in handling objects usually come down to understanding when new ones have been created, when old ones have died, and what variables refer to what objects. The issue described here fits into that last category.
Take an example where you are creating a list of objects in a loop. Typical code looks like this:
Dim Buttons As List(Of Button) = New List(Of Button) Dim ButtonCount As Integer = 6 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load For I As Integer = 1 To ButtonCount Dim B As Button = New Button B.Name = "btnInfo" & I.ToString Dim W As Integer = Me.Width \ (ButtonCount + 1) Dim X As Integer = (W + 1) * I - (W \ 2) Dim Y As Integer = Me.Height - 75 B.Location = New Point(X, Y) B.Size = New Size(W, 20) B.Text = "Info #" & I.ToString B.Anchor = AnchorStyles.Bottom + AnchorStyles.Right AddHandler B.Click, AddressOf infoButton_Click Buttons.Add(B) Me.Controls.Add(B) Next End Sub
This code executes a New command 6 times, so you would expect 6 buttons to be created. As each button is created it is added to the list. The buttons will persist for the life of their entry in the list (either the Buttons list or the Me.Controls list). The code works as you would expect.
But it is very easy to make a small change to the code which causes it to fail. In fact, many beginner programmers wil default to this style of coding:
Dim Buttons As List(Of Button) = New List(Of Button) Dim ButtonCount As Integer = 6 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim B As Button = New Button For I As Integer = 1 To ButtonCount B.Name = "btnInfo" & I.ToString Dim W As Integer = Me.Width \ (ButtonCount + 1) Dim X As Integer = (W + 1) * I - (W \ 2) Dim Y As Integer = Me.Height - 75 B.Location = New Point(X, Y) B.Size = New Size(W, 20) B.Text = "Info #" & I.ToString B.Anchor = AnchorStyles.Bottom + AnchorStyles.Right AddHandler B.Click, AddressOf infoButton_Click Buttons.Add(B) Me.Controls.Add(B) Next End Sub
The first reaction is that this is better code, because there is no need to ‘recreate’ B at the start of each loop iteration – do it once before you start. So why is this code wrong?
It’s wrong because it changes the logic. B is a reference variable (a Button is an object) so each time B = New Button is executed, the variable B is updated to point to a new instance of a Button, and that new instance is what gets added to the list
Buttons() is a list of reference variables – it is a list of references to Button objects. When the code
is executed, the next list item in Buttons is set up so that it refers to whatever object that variable B refers to. So unless B has been set to refer to a new object, each item in the list will be referring to the same object. To express that at the hardware level, the list item and the variable B have the same value, and that value is a memory location that contains the address of the Button object (a ‘pointer’).
Now the loop iterates. B is updated with new property values . But if B has been changed then every item in the list of buttons that has been set to point to whatever B points to has also been changed! The end result is that the Buttons list contains 6 references to the same button.
Creating a new button instance inside the loop ensures that each time a List item is updated it is updated with a new, unique reference to the most-recently created button, and that reference will not be overwritten when another object is created in the next iteration of the loop.
How to avoid the problem?
1. Declare your variable just before you first use it. This helps to ensure (but does not guarantee) that each loop iteration refers to a new object. Note that this is contrary to some structured programming principles that have been promoted in the past, where ‘preparing’ your variables at (or before) the start of a routine was recommended. This rule is another way of saying “Don’t give your variables a broader scope than they need” which is a gneral rule in .Net.
2. If you re-use a variable, for instance in a loop, be aware of whether or not it really is a ‘new’ variable, not just the same variable pointing to a new object, because that can have an impact on your logic.
A variable that points to an instance of an object other than the one that it is supposed to point to is one of the most common errors that occur in .Net programming, and this example is one way in which that problem can occur.