创建一个无法在外部更改的List属性

时间:2013-06-05 08:41:55

标签: vb.net class properties generic-list

我的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时间远远超过他们,可能会有一个更整洁,更清洁的想法。

任何?

2 个答案:

答案 0 :(得分: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方法的速度都是相同的。

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

此外,该列表是真正的只读。它没有AddClear方法,因此使用该对象的其他人不会产生混淆。

或者,如果您只需要让消费者能够遍历列表,那么您可以将该属性公开为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

答案 1 :(得分: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