2013-06-05 22 views
5

Ho una classe pubblica nel mio progetto VB.NET che ha una proprietà List(Of String). Questo elenco deve essere modificato da altre classi all'interno del progetto, ma dal momento che la classe potrebbe (in futuro) essere esposta al di fuori del progetto, voglio che non sia modificabile al livello, a . La modifica della proprietà esistente all'interno del progetto verrà eseguita solo chiamando i metodi dell'elenco (in particolare), non sostituendo il valore della proprietà con un nuovo Elenco (è il motivo per cui lo ho come proprietà ReadOnly).Creare una proprietà Elenco che non può essere modificata esternamente

Sono venuto su con un modo di farlo, ma non sono sicuro che sia esattamente quello che si chiama "elegante".

E 'questo:

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 

Ora questo solo funziona bene e dandy. Qualsiasi classe nel progetto che accede direttamente al campo mlst_ParameterNames può modificarla come necessario, ma qualsiasi procedura che acceda tramite la proprietà pubblica può modificarla a piacimento, ma non porterà da nessuna parte poiché la procedura di proprietà restituisce sempre un copia della lista, non la lista stessa.

Ma, naturalmente, ciò comporta un sovraccarico che è il motivo per cui sento che è solo ... beh, visceralmente "sbagliato" ad un certo livello, anche se funziona.

L'elenco dei parametri non sarà mai enorme. Al massimo conterrà solo 50 voci, ma più comunemente meno di dieci elementi, quindi non riesco a vedere che sia mai stato un killer delle prestazioni. Tuttavia, ovviamente mi ha fatto pensare che qualcuno, con molte più ore VB.NET sotto la cintura, possa avere un'idea molto più pulita e pulita.

Chiunque?

+1

penso che la vostra soluzione è buona sufficienti e non avete bisogno di qualsiasi altra workaroung – SysDragon

+0

@SysDragon: in base al numero di elementi della collezione, sono d'accordo. – Paul

risposta

9

Invece di creare una nuova copia della lista originale, è necessario utilizzare il metodo AsReadOnly a ge t una versione di sola lettura della lista, in questo modo:

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 

Secondo il MSDN:

Questo metodo è un O (1) funzionamento.

Ciò significa che la velocità del metodo AsReadOnly è la stessa, indipendentemente dalle dimensioni dell'elenco.

Oltre ai potenziali vantaggi in termini di prestazioni, la versione di sola lettura dell'elenco viene automaticamente sincronizzata con l'elenco originale, quindi se il codice di consumo mantiene un riferimento ad esso, il suo elenco di riferimento sarà comunque aggiornato data, anche se gli articoli vengono aggiunti o rimossi successivamente dall'elenco.

Inoltre, l'elenco è veramente di sola lettura. Non ha un metodo Add o Clear, quindi ci sarà meno confusione per gli altri che usano l'oggetto.

In alternativa, se tutto ciò che serve è per i consumatori siano in grado di scorrere la lista, allora si può solo esporre la proprietà come IEnumerable(Of String) che è, di per sé, una sola lettura Interfaccia:

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

Tuttavia , che rende utile solo l'accesso all'elenco in un ciclo For Each. Ad esempio, non è possibile ottenere Count o accedere agli elementi nell'elenco per indice.

Come nota a margine, suggerirei di aggiungere una seconda proprietà Friend invece di esporre semplicemente il campo stesso come Friend. Per esempio:

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

Questo è BRILLANTE, grazie. È un modo molto efficace per arrivare dove devo andare. Io lavoro ancora principalmente in VBA e molto come la SM mi infastidisce sempre più in molti modi, non posso fare a meno di essere impressionato da alcune delle cose che hanno incorporato in .Net come questo. Rende la vita molto più facile rispetto alle vecchie collezioni grezze di VB6. Tuttavia le caratteristiche sarebbero inutili in se stesse senza persone che sanno come applicarle in questo modo. Grazie ancora. –

1

Che dire di fornire una proprietà Locked che è possibile impostare, ogni altra proprietà quindi controlla questo per vedere se è stato bloccato ...

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 -

Giusto per aggiungere a questo, allora, ecco una collezione di base ...

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

Un'idea molto interessante ... anche se penso che Locked dovrebbe essere Friend piuttosto che ambito pubblico per impedire modifiche al di fuori del progetto. L'unico problema è che limita l'assegnazione all'ingrosso di una nuova lista alla variabile parametro, mentre (e forse non sono stato abbastanza chiaro nella domanda, dovrei modificare) le azioni interne modificano semplicemente la lista, tipicamente da. Aggiungi o metodi .Clear. Non voglio/devo mai sostituire l'oggetto originale con uno nuovo. Grazie per il suggerimento, però. –

+1

Ah - Capisco; Ho erroneamente interpretato la tua frase * Qualsiasi classe nel progetto che accede direttamente alla variabile mlst_ParameterNames può modificarla come necessario *. Hmm. L'unico altro modo per controllare questo sarebbe rotolare la tua collezione; in questo modo puoi controllare ogni ultimo aspetto di ciò che accade. Potrebbe essere una situazione matta e matta, però. Sono d'accordo con l'ambito 'Amico' però. – Paul