5

我的 VB.NET 项目中有一个公共类,它有一个List(Of String)属性。此列表需要由项目中的其他类修改,但由于该类可能(在将来的某个时间)暴露在项目之外,我希望它在级别是不可修改的。项目中现有属性的修改只能通过调用列表的方法(特别是.Add,偶尔.Clear)来完成,而不是通过用新列表完全替换属性值来完成(这就是我将它作为ReadOnly属性的原因)。

我想出了一种方法,但我不确定它是否正是你所说的“优雅”。

是这样的:

Friend mlst_ParameterNames As List(Of String) = New List(Of String)

Public ReadOnly Property ParameterNames() As List(Of String)
    Get
        Return New List(Of String)(mlst_ParameterNames)
    End Get
End Property

现在这只是工作正常和花花公子。项目中直接访问该mlst_ParameterNames字段的任何类都可以根据需要对其进行修改,但是通过公共属性访问它的任何程序都可以将其修改为他们想要的内容,但是由于属性程序总是返回一个副本,因此将一事无成列表,而不是列表本身。

但是,当然,这会带来开销,这就是为什么我觉得它只是......好吧,在某种程度上,本能地“错误”,即使它有效。

参数列表永远不会很大。最多它只包含 50 项,但更常见的是少于 10 项,所以我看不出这会成为性能杀手。然而,这当然让我想到,拥有更多 VB.NET 小时数的人可能有一个更整洁、更清晰的想法。

任何人?

4

2 回答 2

9

AsReadOnly您应该使用该方法获取列表的只读版本,而不是创建原始列表的新副本,如下所示:

Friend mlst_ParameterNames As List(Of String) = New List(Of String)

Public ReadOnly Property ParameterNames() As ReadOnlyCollection(Of String)
    Get
        Return mlst_ParameterNames.AsReadOnly()
    End Get
End Property

根据MSDN

此方法是 O(1) 操作。

这意味着AsReadOnly无论列表大小如何,该方法的速度都是相同的。

除了潜在的性能优势之外,列表的只读版本会自动与原始列表保持同步,因此如果使用代码保持对它的引用,它的引用列表仍然是最新的,即使项目稍后会添加到列表中或从列表中删除。

此外,该列表是真正只读的。它没有AddorClear方法,因此使用该对象的其他人不会感到困惑。

或者,如果您只需要让消费者能够遍历列表,那么您可以只公开属性,IEnumerable(Of String)它本质上是一个只读接口:

Public ReadOnly Property ParameterNames() As IEnumerable(Of String)
    Get
        Return mlst_ParameterNames
    End Get
End Property

但是,这使得它只在For Each循环中访问列表有用。例如,您无法Count通过索引获取或访问列表中的项目。

作为旁注,我建议添加第二个Friend属性,而不是简单地将字段本身公开为Friend. 例如:

Private _parameterNames As New List(Of String)()

Public ReadOnly Property ParameterNames() As ReadOnlyCollection(Of String)
    Get
        Return _parameterNames.AsReadOnly()
    End Get
End Property

Friend ReadOnly Property WritableParameterNames() As List(Of String)
    Get
        Return _parameterNames
    End Get
End Property
于 2013-06-05T11:54:07.960 回答
1

提供一个Locked您可以设置的属性怎么样,然后每个其他属性检查它以查看它是否已被锁定......

Private m_Locked As Boolean = False
Private mlst_ParameterNames As List(Of String) = New List(Of String)

Public Property ParameterNames() As List(Of String)
    Get
        Return New List(Of String)(mlst_ParameterNames)
    End Get
    Set(value As List(Of String))
        If Not Locked Then
            mlst_ParameterNames = value
        Else
            'Whatever action you like here...
        End If
    End Set
End Property

Public Property Locked() As Boolean
    Get
        Return m_Locked
    End Get
    Set(value As Boolean)
        m_Locked = value
    End Set
End Property

- 编辑 -

只是补充一点,那么,这是一个基本的集合......

''' <summary>
''' Provides a convenient collection base for search fields.
''' </summary>
''' <remarks></remarks>
Public Class SearchFieldList
        Implements ICollection(Of String)

#Region "Fields..."

        Private _Items() As String
        Private _Chunk As Int32 = 16
        Private _Locked As Boolean = False
        'I've added this in so you can decide if you want to fail on an attempted set or not...
        Private _ExceptionOnSet As Boolean = False

        Private ptr As Int32 = -1
        Private cur As Int32 = -1

#End Region
#Region "Properties..."

        Public Property Items(ByVal index As Int32) As String
            Get
                'Make sure we're within the index bounds...
                If index < 0 OrElse index > ptr Then
                    Throw New IndexOutOfRangeException("Values between 0 and " & ptr & ".")
                Else
                    Return _Items(index)
                End If
            End Get
            Set(ByVal value As String)
                'Make sure we're within the index bounds...
                If index >= 0 AndAlso Not _Locked AndAlso index <= ptr Then
                    _Items(index) = value
                ElseIf _ExceptionOnSet Then
                    Throw New IndexOutOfRangeException("Values between 0 and " & ptr & ". Use Add() or AddRange() method to append fields to the collection.")
                End If
            End Set
        End Property

        Friend Property ChunkSize() As Int32
            Get
                Return _Chunk
            End Get
            Set(ByVal value As Int32)
                _Chunk = value
            End Set
        End Property

        Public ReadOnly Property Count() As Integer Implements System.Collections.Generic.ICollection(Of String).Count
            Get
                Return ptr + 1
            End Get
        End Property
        ''' <summary>
        ''' Technically unnecessary, just kept to provide coverage for ICollection interface.
        ''' </summary>
        ''' <returns>Always returns false</returns>
        ''' <remarks></remarks>
        Public ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.Generic.ICollection(Of String).IsReadOnly
            Get
                Return False
            End Get
        End Property

#End Region
#Region "Methods..."

        Public Shadows Sub Add(ByVal pItem As String) Implements System.Collections.Generic.ICollection(Of String).Add
            If Not _Items Is Nothing AndAlso _Items.Contains(pItem) Then Throw New InvalidOperationException("Field already exists.")
            ptr += 1
            If Not _Items Is Nothing AndAlso ptr > _Items.GetUpperBound(0) Then SetSize()
            _Items(ptr) = pItem
        End Sub

        Public Shadows Sub AddRange(ByVal collection As IEnumerable(Of String))
            Dim cc As Int32 = collection.Count - 1
            For sf As Int32 = 0 To cc
                If _Items.Contains(collection.ElementAt(sf)) Then
                    Throw New InvalidOperationException("Field already exists [" & collection.ElementAt(sf) & "]")
                Else
                    Add(collection.ElementAt(sf))
                End If
            Next
        End Sub

        Public Function Remove(ByVal item As String) As Boolean Implements System.Collections.Generic.ICollection(Of String).Remove
            Dim ic As Int32 = Array.IndexOf(_Items, item)
            For lc As Int32 = ic To ptr - 1
                _Items(lc) = _Items(lc + 1)
            Next lc
            ptr -= 1
        End Function

        Public Sub Clear() Implements System.Collections.Generic.ICollection(Of String).Clear
            ptr = -1
        End Sub

        Public Function Contains(ByVal item As String) As Boolean Implements System.Collections.Generic.ICollection(Of String).Contains
            Return _Items.Contains(item)
        End Function

        Public Sub CopyTo(ByVal array() As String, ByVal arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of String).CopyTo
            _Items.CopyTo(array, arrayIndex)
        End Sub

#End Region
#Region "Private..."

        Private Sub SetSize()
            If ptr = -1 Then
                ReDim _Items(_Chunk)
            Else
                ReDim Preserve _Items(_Items.GetUpperBound(0) + _Chunk)
            End If
        End Sub

        Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of String) Implements System.Collections.Generic.IEnumerable(Of String).GetEnumerator
            Return New GenericEnumerator(Of String)(_Items, ptr)
        End Function

        Private Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
            Return GetEnumerator()
        End Function

#End Region

End Class

Friend Class GenericEnumerator(Of T)
        Implements IEnumerator(Of T)

#Region "fields..."

        Dim flist() As T
        Dim ptr As Int32 = -1
        Dim size As Int32 = -1

#End Region
#Region "Properties..."

        Public ReadOnly Property Current() As T Implements System.Collections.Generic.IEnumerator(Of T).Current
            Get
                If ptr > -1 AndAlso ptr < size Then
                    Return flist(ptr)
                Else
                    Throw New IndexOutOfRangeException("=" & ptr.ToString())
                End If
            End Get
        End Property

        Public ReadOnly Property Current1() As Object Implements System.Collections.IEnumerator.Current
            Get
                Return Current
            End Get
        End Property

#End Region
#Region "Constructors..."


        Public Sub New(ByVal fieldList() As T, Optional ByVal top As Int32 = -1)
            flist = fieldList
            If top = -1 Then
                size = fieldList.GetUpperBound(0)
            ElseIf top > -1 Then
                size = top
            Else
                Throw New ArgumentOutOfRangeException("Expected integer 0 or above.")
            End If
        End Sub

#End Region
#Region "Methods..."

        Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
            ptr += 1
            Return ptr <= size
        End Function

        Public Sub Reset() Implements System.Collections.IEnumerator.Reset
            ptr = -1
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            GC.SuppressFinalize(Me)
        End Sub

#End Region

End Class
于 2013-06-05T09:07:10.630 回答