【问题标题】:Create a List property which cannot be changed externally创建一个无法在外部更改的 List 属性
【发布时间】:2013-06-05 08:41:54
【问题描述】:

我的 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 小时数的人可能有一个更整洁、更清晰的想法。

有人吗?

【问题讨论】:

  • 我认为您自己的解决方案已经足够好,您不需要任何其他解决方法
  • @SysDragon:基于集合中元素的数量,我同意。

标签: vb.net class properties generic-list


【解决方案1】:

您应该使用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

【讨论】:

  • 这太棒了,谢谢。这是到达我需要去的地方的高效方式。我仍然主要在 VBA 中工作,就像 MS 在许多方面越来越让我烦恼一样,我不禁对他们在 .Net 中内置的一些东西印象深刻,比如这个。它比 VB6 中笨重的旧 Collections 让生活变得容易得多。但是,如果没有知道如何以这种方式应用它们的人,这些功能本身将毫无用处。再次感谢。
【解决方案2】:

提供一个您可以设置的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

【讨论】:

  • 非常有趣的想法......虽然我认为 Locked 需要成为 Friend 而不是 public 范围以防止在项目之外进行修改。唯一的问题是它将新列表的批量分配限制为参数变量,而(我可能对问题不够清楚;我将修改)内部操作仅修改列表,通常通过 .添加或 .Clear 方法。我不想/不需要用新对象替换原始对象。不过,感谢您的建议。
  • 啊 - 我明白了;我误解了您的短语项目中直接访问 mlst_ParameterNames 变量的任何类都可以根据需要对其进行修改。唔。控制这种情况的唯一其他方法是滚动您自己的收藏;这样你就可以控制所发生的每一个方面。不过,这可能是一个大锤和坚果的情况。不过我同意Friend 范围。
猜你喜欢
  • 1970-01-01
  • 2021-04-26
  • 1970-01-01
  • 1970-01-01
  • 2013-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-16
相关资源
最近更新 更多