我的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时间远远超过他们,可能会有一个更整洁,更清洁的想法。
任何?
答案 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
方法的速度都是相同的。
除了潜在的性能优势之外,列表的只读版本会自动与原始列表保持同步,因此如果使用代码保留对它的引用,则其引用的列表仍将是最新的,即使稍后将项目添加到列表中或从列表中删除。
此外,该列表是真正的只读。它没有Add
或Clear
方法,因此使用该对象的其他人不会产生混淆。
或者,如果您只需要让消费者能够遍历列表,那么您可以将该属性公开为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