2013-06-05 7 views
5

Mam klasy publiczne w moim projekcie VB.NET, który ma właściwość List(Of String). Ta lista musi zostać zmodyfikowana przez inne klasy w ramach projektu, ale ponieważ klasa może (w pewnym momencie w przyszłości) zostać ujawniona poza projektem, chcę, aby była niemodyfikowalna na poziomie na poziomie. Modyfikacja istniejącej nieruchomości w ramach projektu zostanie wykonana tylko przez wywołanie metod listy (szczególnie .Add, sporadycznie .Clear), a nie przez hurtowe zastąpienie wartości nieruchomości nową listą (dlatego mam ją jako właściwość ReadOnly).Tworzenie właściwości listy, której nie można zmienić zewnętrznie

mam wymyślić sposób to zrobić, ale nie jestem pewien, że to jest dokładnie to, co można by nazwać „elegancki”.

Jest to:

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 

Teraz to po prostu działa cacy. Każda klasa w projekcie, która uzyskuje dostęp do pola mlst_ParameterNames bezpośrednio, może ją modyfikować w razie potrzeby, ale wszelkie procedury, które uzyskują dostęp do niej za pośrednictwem właściwości publicznej, mogą odegrać kluczową rolę w modyfikacji jej zawartości, ale nie będą miały miejsca, ponieważ procedura właściwości zawsze zwraca kopię listy, a nie samą listę.

Ale oczywiście, że niesie ze sobą obciążenie, dlatego uważam, że jest po prostu ... no cóż, trzewiowo "źle" na pewnym poziomie, nawet jeśli działa.

Lista parametrów nigdy nie będzie ogromna. Co najwyżej zawiera tylko 50 pozycji, ale zwykle mniej niż dziesięć przedmiotów, więc nie widzę, by kiedykolwiek był to performer-zabójca. Jednak oczywiście przekonał mnie, że ktoś z dużo większą liczbą godzin VB.NET może mieć o wiele lepszy i bardziej przejrzysty pomysł.

Ktoś?

+1

myślę własne rozwiązanie jest dobre oraz wystarczająco i nie trzeba żadnych innych workaroung – SysDragon

+0

@SysDragon: na podstawie liczby elementów w kolekcji, zgadzam się. – Paul

Odpowiedz

9

Zamiast tworzyć nową kopię oryginalnej listy, należy użyć metody GE AsReadOnly T a wersji tylko do odczytu listy, na przykład:

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 

Według MSDN:

Ta metoda jest O (1) działanie.

co oznacza, że ​​prędkość metody AsReadOnly jest taka sama, niezależnie od wielkości listy.

Oprócz potencjalnych korzyści związanych z wydajnością, lista w wersji tylko do odczytu jest automatycznie synchronizowana z oryginalną listą, więc jeśli zużyty kod zachowuje odniesienie do niej, lista, do której się odnosi, będzie nadal aktualna. data, nawet jeśli elementy zostaną później dodane lub usunięte z listy.

Ponadto lista jest naprawdę tylko do odczytu. Nie ma metody Add lub Clear, więc nie będzie mniej zamieszania dla innych osób korzystających z obiektu.

Alternatywnie, jeśli wszystko, co potrzebne jest, aby konsumenci mogli iterację na liście, a następnie można po prostu narazić właściwość jako IEnumerable(Of String) który jest z natury, interfejs read-only:

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

jednak , co czyni dostęp tylko do listy w pętli For Each. Nie można na przykład uzyskać wartości Count lub uzyskać dostępu do elementów na liście według indeksu.

Na marginesie, polecam dodać drugą właściwość Friend, zamiast po prostu eksponować pole, jako Friend. Na przykład:

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

To BRILLIANT, dziękuję. To bardzo skuteczny sposób dotarcia tam, gdzie muszę iść. Nadal pracuję głównie w VBA i tak jak MS coraz bardziej irytuje mnie na wiele sposobów, nie mogę nie być pod wrażeniem niektórych rzeczy, które wbudowali w .Net, takich jak ten. To sprawia, że ​​życie jest o wiele łatwiejsze niż niezgrabne stare kolekcje z VB6. Jednak funkcje byłyby bezużyteczne same w sobie bez ludzi, którzy wiedzą, jak je zastosować w ten sposób. Dzięki jeszcze raz. –

1

Co o zapewnienie właściwość Locked, które można ustawić, każda inna własność następnie sprawdza to, aby sprawdzić, czy został zablokowany ...

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 -

Wystarczy dodać do to, to tutaj jest podstawowy zbiór ...

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

Bardzo ciekawy pomysł ... choć myślę, że Locked musiałby być przyjacielem, a nie publicznym, aby zapobiec modyfikacjom poza projektem. Jedyny problem polega na tym, że ogranicza on hurtowe przyporządkowanie nowej listy do zmiennej parametrów, podczas gdy (i może nie byłem wystarczająco jasny w pytaniu, będę modyfikował) działania wewnętrzne jedynie modyfikują listę, zazwyczaj przez. Dodaj lub .Wyczyść metody. Nie chcę/nie chcę wymieniać oryginalnego obiektu na nowy. Dzięki za sugestię. –

+1

Ah - Rozumiem; Źle zinterpretowałem twoją frazę * Dowolna klasa w projekcie, która bezpośrednio uzyskuje dostęp do zmiennej mlst_ParameterNames, może ją w razie potrzeby zmodyfikować *. Hmm. Jedynym innym sposobem kontrolowania tego byłoby rolowanie własnej kolekcji; w ten sposób możesz kontrolować każdy ostatni aspekt tego, co się dzieje. To może być sytuacja z młotem i nakrętką. Zgadzam się jednak z zakresem 'Przyjaciela'. – Paul

Powiązane problemy