2013-06-05 6 views
5

Ich habe eine öffentliche Klasse in meinem VB.NET-Projekt, die eine List(Of String) Eigenschaft hat. Diese Liste muss von anderen Klassen innerhalb des Projekts geändert werden, aber da die Klasse (irgendwann in der Zukunft) außerhalb des Projekts verfügbar gemacht werden kann, möchte ich, dass sie unter der Ebene nicht änderbar ist. Die Änderung der vorhandenen Eigenschaft innerhalb des Projekts erfolgt nur durch Aufruf der Methoden der Liste (insbesondere .Add, gelegentlich .Clear), nicht durch eine umfassende Ersetzung des Grundstückswerts durch eine neue Liste (weshalb ich es als ReadOnly Eigentum habe).Erstellen Sie eine List-Eigenschaft, die nicht extern geändert werden kann

Ich habe mit eine Art und Weise, es zu tun kommen, aber ich bin mir nicht sicher, dass es genau das ist, was Sie „elegant“ nennen würden.

Es ist diese:

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 

Jetzt funktioniert dies nur schön und gut. Jede Klasse im Projekt, die direkt auf das Feld mlst_ParameterNames zugreift, kann sie nach Bedarf modifizieren, aber alle Prozeduren, die über die public-Eigenschaft darauf zugreifen, können nach Herzenslust abknallen, werden aber nichts erreichen, da die Eigenschaftsprozedur immer eine zurückgibt Kopie der Liste, nicht die Liste selbst.

Aber natürlich, dass Overhead trägt, weshalb ich fühle, dass es nur ... na ja, viszeral "falsch" auf einer Ebene, obwohl es funktioniert.

Die Parameterliste wird nie groß sein. Es werden höchstens 50, meistens aber weniger als zehn Elemente enthalten sein, also kann ich nicht sehen, dass dies jemals ein Performance-Killer ist. Allerdings hat es mich natürlich dazu gebracht, zu denken, dass jemand, der weit mehr VB.NET-Stunden auf dem Buckel hat, eine viel sauberere und sauberere Idee haben könnte.

Jeder?

+1

Ich denke, Ihre eigene Lösung genug gut ist, und Sie brauchen nicht, andere workaroung – SysDragon

+0

@SysDragon: basierend auf der Anzahl von Elementen in der Sammlung, ich bin einverstanden. – Paul

Antwort

9

Statt eine neue Kopie der ursprünglichen Liste zu erstellen, sollten Sie die AsReadOnly Methode ge verwenden t eine schreibgeschützte Version der Liste, wie folgt aus:

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 

Nach dem MSDN:

Diese Methode ist eine O (1) -Operation.

Das bedeutet, dass die Geschwindigkeit der AsReadOnly Methode die gleiche ist, unabhängig von der Größe der Liste.

Zusätzlich zu den potenziellen Leistungsvorteilen wird die schreibgeschützte Version der Liste automatisch mit der ursprünglichen Liste synchronisiert. Wenn also der verbrauchende Code einen Verweis darauf enthält, ist die referenzierte Liste weiterhin auf dem neuesten Stand. Datum, auch wenn Elemente später hinzugefügt oder aus der Liste entfernt werden.

Auch die Liste ist wirklich schreibgeschützt. Es hat keine Add oder Clear Methode, so dass es weniger Verwirrung für andere gibt, die das Objekt verwenden.

Alternativ kann, wenn alles, was Sie brauchen, ist für die Verbraucher durch die Liste zu durchlaufen zu können, dann können Sie nur die Eigenschaft als IEnumerable(Of String) belichten das ist, von Natur aus, eine Nur-Lese-Schnittstelle:

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

jedoch , das macht es nur sinnvoll, auf die Liste in einer For Each Schleife zuzugreifen. Sie können beispielsweise nicht Count abrufen oder auf die Elemente in der Liste nach Index zugreifen.

Als eine Randnotiz würde ich empfehlen, eine zweite Friend Eigenschaft hinzuzufügen, anstatt einfach das Feld selbst als Friend auszusetzen. Zum Beispiel:

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 
+2

Das ist brillant, danke. Es ist eine sehr effiziente Art, dorthin zu gelangen, wo ich hingehen muss. Ich arbeite immer noch hauptsächlich in VBA und so sehr, wie MS mich in vielerlei Hinsicht zunehmend nervt. Ich kann nicht anders, als von einigen der Dinge beeindruckt zu sein, die sie in .Net wie diesem eingebaut haben. Es macht das Leben so viel einfacher als die klobigen alten Kollektionen von VB6. Allerdings wären die Features für sich selbst nutzlos, wenn sie nicht auf diese Weise angewendet werden könnten. Danke noch einmal. –

1

Was eine Locked Eigenschaft über die Bereitstellung, die Sie festlegen können, die jeweils andere Eigenschaft überprüft dann, dies zu sehen, ob es gesperrt worden ist ...

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 

- EDIT -

Gerade in der dies, dann ist hier eine grundlegende Sammlung ...

''' <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 
+0

Sehr interessante Idee ... obwohl ich denke, dass Locked eher Friend als öffentlicher Bereich sein müsste, um Veränderungen außerhalb des Projekts zu verhindern. Das einzige Problem dabei ist, dass es die Großhandelszuweisung einer neuen Liste auf die Parametervariable einschränkt, während die internen Aktionen lediglich die Liste modifizieren (normalerweise war ich in der Frage nicht klar genug; ich werde sie modifizieren). Fügen Sie Methoden hinzu oder .Clear. Ich möchte/will das ursprüngliche Objekt niemals durch ein neues ersetzen. Danke für den Vorschlag. –

+1

Ah - ich verstehe; Ich habe Ihren Ausdruck falsch interpretiert * Jede Klasse im Projekt, die direkt auf die Variable mlst_ParameterNames zugreift, kann sie nach Bedarf modifizieren *. Hmm. Die einzige andere Möglichkeit, dies zu kontrollieren, wäre, eine eigene Sammlung zu erstellen. Auf diese Weise können Sie jeden letzten Aspekt dessen, was passiert, kontrollieren. Das könnte allerdings eine Vorschlaghammer- und Nuss-Situation sein. Ich stimme dem Anwendungsbereich "Freund" zu. – Paul

Verwandte Themen