Home > Blockchain >  Why does this code seem to pass references, when I specify byval?
Why does this code seem to pass references, when I specify byval?

Time:03-31

I'm currently coding a program to monitor certain computers on our network. To this end, I have a Monitor object, that I have made a singleton, to ensure that all functions that communicate with it, get the same data. This object maintains a list of ComputerData objects, and provides the public functions to add, remove, and update these objects.

I've written a quick test harness that gets the instance of the monitor, and then sends a couple of testdata ComputerData objects (passing them using a BYVAL)

However I notice that if I change the testdata ComputerData object, AFTER I have passed it to monitor, that the object stored in Monitors list is also updated. Like it had been passed as BYREF.

Public Class Monitor
Dim lock As New Object
Private _MonitoredComputers As New List(Of ComputerData)

ReadOnly Property MonitoredComputers As List(Of ComputerData)
    Get
        SyncLock lock
            Return _MonitoredComputers
        End SyncLock
    End Get
End Property

Private Shared ReadOnly _instance As New Lazy(Of Monitor)(Function() New Monitor(), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication)

Public Shared ReadOnly Property Instance As Monitor
    Get
        Return _instance.Value
    End Get
End Property

Public Sub Add(ByVal computer As ComputerData)
    SyncLock lock
        If _MonitoredComputers.Contains(computer) Then
            Throw New Exception($"List already contains {computer}")
        Else
            _MonitoredComputers.Add(computer)
        End If
    End SyncLock
End Sub

Public Sub Remove(ByVal computer As ComputerData)
    SyncLock lock
        If Not _MonitoredComputers.Contains(computer) Then
            Throw New Exception($"{computer} is not a member of list")
        Else
            _MonitoredComputers.Remove(computer)
        End If
    End SyncLock
End Sub

Public Sub Update(ByVal computer As ComputerData)
    If Not _MonitoredComputers.Contains(computer) Then
        Add(computer)
    Else
        Remove(computer)
        Add(computer)
    End If
End Sub

Private Sub New()

End Sub

End Class

ComputerData Object

Public Class ComputerData
Property Name As String
Property IP As String
Property Online As Boolean
Property TimeAdded As Date
Property LastPing As Date
Property OnlineUser As String
Property LoggedIn As Boolean

Public Overloads Function Equals(ByVal computer As ComputerData) As Boolean
    Return computer.IP = IP AndAlso IP = computer.IP
End Function

Public NotOverridable Overrides Function Equals(ByVal obj As Object) As Boolean
    Dim temp = TryCast(obj, ComputerData)
    If temp IsNot Nothing Then Return Me.Equals(temp)
    Return False
End Function

Public Shared Operator =(ByVal c1 As ComputerData, ByVal c2 As ComputerData) As Boolean
    Return c1.Equals(c2)
End Operator

Public Shared Operator <>(ByVal c1 As ComputerData, ByVal c2 As ComputerData) As Boolean
    Return Not c1 = c2
End Operator

Public Overrides Function GetHashCode() As Integer
    Return IP.GetHashCode
End Function
End Class

Test Harness

Public Class TestFE
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim mon As Monitor = Monitor.Instance
    Dim td1 As New ComputerData With {
        .IP = "192.168.1.1",
        .Name = "Test1",
        .Online = True}

    Dim td2 As New ComputerData With {
        .IP = "192.168.1.200",
        .Name = "Test3",
        .Online = True}

    mon.Add(td1)

    td1.Name = "test2"

    mon.Update(td1)

    mon.Add(td2)

    td2.Name = "How did this change?"

    Dim clist As List(Of ComputerData) = mon.MonitoredComputers
    For Each c As ComputerData In clist
        ListBox1.Items.Add(c.Name)
    Next
End Sub
End Class

Expected Output

Test2
Test3

Actual Output

Test2
How did this change?

I know how to correct this. In monitor I would create a new ComputerData object, and copy the values from the passed object to the new object and add that to the list.

My question is, why does the code as written not perform as I expect?

CodePudding user response:

Passing by value means passing a copy of the contents of a variable. If a variable is a reference type then the variable contains a reference to an object. Passing that by value means passing a copy of the reference, not a copy of the object. Passing a method parameter by value is exactly the same as assigning one variable to another. If you did this:

Dim var1 As New List(Of String)
Dim var2 = var1

var2.Add("Hello World")

Console.WriteLine(var1.Count)

what would you expect to see displayed? I hope you said "1" rather than "0", because there is only one List(Of String) object. If there's only one object then it doesn't matter which variable you access it by. It's just like object's in real life. If your father put on a red shirt and then your mother's husband put on a blue shirt (assuming that your parents are married) what colour shirt would you expect to see your father wearing? I hope you said "blue". He's still the same person, regardless of how you reference him. This code is basically the same as above:

Private Sub DoSomething(var2 As List(Of String))
    var2.Add("Hello World")
End Sub

and:

Dim var1 As New List(Of String)

DoSomething(var1)

Console.WriteLine(var1.Count)

Now that you know how reference typ4es behave, take another look at your code and, preferably, run it in the debugger and watch what happens.

CodePudding user response:

td1 and td2 still exist in your scope (you declare them in the very same sub), so if you pass them to the list, you only add references to these. Any change on the objects get reflected in the list items, since they are the same. On the other hand, the mon.Update(td1) is redundant in this context.

The variables you declare live as long as the context lives.

just to test:

mon(0).Equals(td1) 'should be true
mon(1).equals(td2) 'should be true as well
  • Related