当我指定 byval 时,为什么这段代码似乎传递了引用?

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

我目前正在编写一个程序来监控我们网络上的某些计算机。 为此,我有一个 Monitor 对象,我已经创建了一个单例,以确保与其通信的所有函数都获得相同的数据。 此对象维护一个 ComputerData 对象列表,并提供 public 函数来添加、删除和更新这些对象。

我编写了一个获取监视器实例的快速测试工具,然后发送几个测试数据 ComputerData 对象(使用 BYVAL 传递它们)

但是我注意到,如果我更改了 testdata ComputerData 对象,在将其传递给监视器之后,存储在监视器列表中的对象也会更新。就像它作为 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

计算机数据对象

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

测试工具

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

预期输出

Test2
Test3

实际输出

Test2
How did this change?

我知道如何纠正这个问题。在监视器中,我将创建一个新的 ComputerData 对象,并将传递的对象中的值复制到新对象并将其添加到列表中。

我的问题是,为什么编写的代码没有达到我预期的效果?

按值传递意味着传递变量内容的副本。如果变量是引用类型,则该变量包含对 object 的引用。按值传递它意味着传递引用的副本,而不是 object 的副本。按值传递方法参数与将一个变量分配给另一个变量完全相同。如果您这样做:

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

var2.Add("Hello World")

Console.WriteLine(var1.Count)

您希望看到什么?我希望你说的是“1”而不是“0”,因为只有一个 List(Of String) object。如果只有一个 object 那么通过哪个变量访问它并不重要。就像现实生活中的 object 一样。如果您的父亲穿上红色衬衫,然后您母亲的丈夫穿上蓝色衬衫(假设您 parents 已婚),您希望看到您父亲穿什么颜色的衬衫?我希望你说的是“蓝色”。不管你怎么称呼他,他仍然是同一个人。这段代码和上面基本一样:

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

和:

Dim var1 As New List(Of String)

DoSomething(var1)

Console.WriteLine(var1.Count)

既然您知道引用 typ4es 的行为方式,请再看一下您的代码,最好是在调试器中 运行 看看会发生什么。

td1td2 仍然存在于您的作用域中(您在同一个子项中声明它们),因此如果将它们传递给列表,您只添加对它们的引用。对象的任何更改都会反映在列表项中,因为它们是相同的。另一方面,mon.Update(td1) 在这种情况下是多余的。

只要上下文存在,您声明的变量就会存在。

只是为了测试:

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