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