是否可以创建固定长度的数组属性

时间:2013-12-02 21:04:38

标签: arrays vb.net properties

我有一种情况需要创建一个存储子项列表的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类上创建一个公共属性,它包含一个固定长度的项目列表,其中项目的值可以更改,但项目的数量不能。

2 个答案:

答案 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