我有一种情况需要创建一个存储子项列表的DTO类,但需要修复列表中子项的数量。由于它是DTO,我需要能够修改列表中每个项目的值,但我不希望消费代码能够在列表中添加或删除项目。
举个例子,假设我需要一个Person
类,我需要Person
类来存储一个电话号码列表。为了举例,我们可以说一个人只能拥有三个电话号码。在任何其他语言中,我只是将列表创建为数组,因为其他语言的数组无法调整大小。但是,在VB.NET中,即使使用ReDim
属性,也可以随时在任何数组上调用VBFixedArray
。例如,这不做我需要的事情:
Public Sub Main()
Dim p As New Person()
p.PhoneNumbers(0) = "555-555-5555"
ReDim Preserve p.PhoneNumbers(5) ' Successfuly changes the size of the array--not good
End Sub
Public Class Person
<VBFixedArray(2)>
Public PhoneNumbers As String() = {"", "", ""}
End Class
如果我尝试使用IReadOnlyList
,则大小会变得固定,但项目本身也是如此。例如,这不起作用:
Public Sub Main()
Dim p As New Person()
p.PhoneNumbers(0) = "555-555-5555" ' Compile error: Property 'Item' is 'ReadOnly'.
End Sub
Public Class Person
Public ReadOnly Property PhoneNumbers As IReadOnlyList(Of String)
Get
Return _phoneNumbers.AsReadOnly()
End Get
End Property
Private _phoneNumbers As New List(Of String)({"", "", ""})
End Class
那么,我怎么能在VB.NET中在我的DTO类上创建一个公共属性,它包含一个固定长度的项目列表,其中项目的值可以更改,但项目的数量不能。
答案 0 :(得分:3)
在VB.NET中,使用ReDim
可以调整所有数组的大小,但这实际上是一种错觉。底层MSIL实际上不支持可调整大小的数组。当您在VB.NET中调用数组ReDim Preserve
时,它不会调整现有数组的大小 - 它实际上会创建一个新数组,将数据从旧数组复制到新数组,然后指向数组变量到新阵列。请记住,.NET中的数组是引用类型。例如:
Dim test1() As String = {"1", "2", "3"}
Dim test2() As String = test1
test2(0) = "after"
Console.WriteLine(String.Join(", ", test1)) ' Outputs "after, 2, 3"
因此,如果为数组创建属性,则只要使用ReDim
调整属性数组的大小,就会调用property-setter。例如:
Public Sub Main()
Dim p As New Person()
ReDim p.PhoneNumbers(3) ' Outputs "Setter called"
End Sub
Public Class Person
Public Property PhoneNumbers As String()
Get
Return _phoneNumbers
End Get
Set(value As String())
Console.WriteLine("Setter called")
_phoneNumbers = value
End Set
End Property
Private _phoneNumbers As String()
End Class
因此,如果要创建固定长度列表,可以更改项目,但不能更改项目数,可以创建一个只读属性,返回一个数组,如下所示:
Public Sub Main()
Dim p As New Person()
p.PhoneNumbers(0) = "555-555-5555" ' Works
ReDim p.PhoneNumbers(5) ' Compile error: Property 'PhoneNumbers' is 'ReadOnly'.
End Sub
Public Class Person
Public ReadOnly Property PhoneNumbers As String()
Get
Return _phoneNumbers
End Get
End Property
Private _phoneNumbers(2) As String
End Class
或者,如果您不希望将列表公开为数组,则可以使该属性返回IList
,如下所示:
Public Sub Main()
Dim p As New Person()
p.PhoneNumbers(0) = "555-555-5555" ' Works
Console.WriteLine(p.PhoneNumbers.IsReadOnly) ' Outputs "True"
p.PhoneNumbers.Add("555-555-5555") ' Compiles, but throws a NotSupportedException: "Collection was of a fixed size."
End Sub
Public Class Person
Public ReadOnly Property PhoneNumbers As IList(Of String)
Get
Return _phoneNumbers
End Get
End Property
Private _phoneNumbers(2) As String
End Class
你会认为你可以让属性返回IReadOnlyList
,但是如果你这样做,那么你就不能改变列表中任何项目的值(至少没有强制转换它) ,例如:
Public Sub Main()
Dim p As New Person()
p.PhoneNumbers(0) = "555-555-5555" ' Compile error: Property 'Item' is 'ReadOnly'.
End Sub
Public Class Person
Public ReadOnly Property PhoneNumbers As IReadOnlyList(Of String)
Get
Return _phoneNumbers
End Get
End Property
Private _phoneNumbers(2) As String
End Class
答案 1 :(得分:2)
要考虑的另一个选择是使PhoneNumbers属性参数化(仅返回字符串而不是数组)。这会强制消费代码一次获得一个电话号码,这对您来说可能是也可能不是问题。我在这个代码示例中使用了List(of String),但它很容易适应使用数组。
Public Class Person
Private _lstPhones As New List(Of String)
Private Const _intPhoneUpperBounds As Integer = 3
Public Sub New()
For intCursor As Integer = 0 To _intPhoneUpperBounds
_lstPhones.Add(String.Empty)
Next
End Sub
Public Property Phones(ByVal intIndex As Integer) As String
Get
If _lstPhones.Count > intIndex Then
Return _lstPhones(intIndex)
Else
'Or exception.
Return String.Empty
End If
End Get
Set(ByVal value As String)
If intIndex < _intPhoneUpperBounds Then
_lstPhones(intIndex) = value
Else
'Throw Exception
End If
End Set
End Property
End Class