为什么 ReadOnly 属性 是全局可变的?

Why ReadOnly Property are globally changeable?

我的孔应用程序有一个数据 class。此数据 class 可从应用程序中的许多其他 class 访问。我一开始就加载数据,希望数据全局不可更改。

这是现在的样子(示例)

NotInheritable Class Data
Public NotInheritable Class Country
    Public CounCode As String = String.Empty
    Public ISOCode As String = String.Empty
    Public Name As String = String.Empty

    Public Overrides Function ToString() As String
        Return CounCode + ": " + ISOCode + " - " + Name
    End Function
End Class

Private Shared m_Countries As List(Of Country) = Nothing
Public Shared ReadOnly Property Countries As List(Of Country)
    Get
        Return m_Countries
    End Get
End Property
Shared Sub New()
    m_Countries = New List(Of Country)
    Dim Country As Country = New Country
    Country.Name = "Germany"
    m_Countries.Add(Country)
    Country.Name = "Italy"
    m_Countries.Add(Country)
    Country.Name = "Spain"
    m_Countries.Add(Country)
End Sub

结束Class

但是当我这样做的时候

    Dim countries As List(Of Data.Country) = Data.Countries
    countries(0).Name = "Hello World"

    Dim countries2 As List(Of Data.Country) = Data.Countries
    MessageBox.Show(countries(0).Name + " - " + countries2(0).Name)

我预计 "Hello World - Germany" 但我会得到 "Hello World - Hello World"。 为什么我可以 - 全局 - 更改 属性?以及如何解决这个问题? 我想允许其他 classes 更改国家名称,但不能更改全球名称。

此致

集合 属性 即 read-only - 不是集合本身,也不是集合中的项目。您尚未将集合重新分配给新集合,这是 only 字段或 属性 访问器阻止的事情。它运行正常。

拥有一个集合 属性 只读 不会 使集合本身 read-only(您仍然可以 add/remove/etc),并且 使集合中的项目read-only。对于 read-only 系列:尝试 ReadOnlyCollection<T>。对于项目本身:您必须 Country.Name read-only.

不能做的(在构造函数之外)是:

Data.Countries = New List(Of Country)

正如 Marc 所说,没有办法阻止这种情况。您不能为 属性 分配不同的集合,但可以更改 属性 封装的集合 (add/remove/change)

解决方法如下:

Imports System.Linq

Module StartupModule

    Sub Main()
        Dim users As New List(Of User) From {
            New User() With {.Name = "John"},
            New User() With {.Name = "Nick"}
        }

        Dim company As New Company(users)


        Console.WriteLine("names before change")
        For Each u In company.Users
            Console.WriteLine(u)
        Next
        Console.WriteLine()

        Console.WriteLine("change names")
        For Each u In company.Users
            u.Name = "something else"
        Next
        Console.WriteLine()

        Console.WriteLine("names after change")
        For Each u In company.Users
            Console.WriteLine(u)
        Next

        Console.ReadLine()
    End Sub


    Public Class Company
        Private _users As New List(Of User)    

        Public Sub New(users As List(Of User))
            Me.Users = users
        End Sub    

        Public Property Users As List(Of User)
            Get
                Return (From u In _users
                       Select DirectCast(u.Clone, User)).ToList
            End Get
            Private Set(value As List(Of User))
                _users = value
            End Set
        End Property
    End Class


    Public Class User
        Implements ICloneable

        Public Property Name As String

        Public Overrides Function ToString() As String
            Return Name
        End Function

        Public Function Clone() As Object Implements ICloneable.Clone
            Return New User() With {.Name = Name}
        End Function
    End Class

End Module

属性的getter总是return原始对象的克隆集合。所以你必须为你的 class 实现 ICloneable 或者只是创建一个函数来 return 你的对象的克隆。

这样,更改是在克隆集合中进行的,而不是更改实际对象。

该方法的问题在于,每当您引用集合(在示例中为 Company.Users 集合)时,都会创建一个新列表,其中填充了原始项目的克隆。