2013-06-05 28 views
5

Tôi có một lớp công khai trong dự án VB.NET của tôi có thuộc tính List(Of String). Danh sách này cần phải được sửa đổi bởi các lớp khác trong dự án, nhưng kể từ khi lớp có thể (tại một thời điểm nào đó trong tương lai) được tiếp xúc bên ngoài dự án, tôi muốn nó không thể sửa đổi ở mức mà mức. Việc sửa đổi thuộc tính hiện tại trong dự án sẽ chỉ được thực hiện bằng cách gọi các phương thức của danh sách (đặc biệt là .Add, đôi khi .Clear), chứ không phải bằng cách thay thế bán buôn giá trị bất động sản với Danh sách mới (đó là lý do tại sao tôi có nó là tài sản ReadOnly).Tạo thuộc tính Danh sách không thể thay đổi bên ngoài

tôi đã đưa ra một cách để làm việc đó, nhưng tôi không chắc chắn rằng đó là chính xác những gì bạn sẽ gọi là "thanh lịch".

Đó là điều này:

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 

Bây giờ này chỉ hoạt động tốt và dandy. Bất kỳ lớp nào trong dự án truy cập trực tiếp vào trường mlst_ParameterNames đều có thể sửa đổi nó khi cần, nhưng mọi thủ tục truy cập nó thông qua tài sản công cộng có thể làm tan biến nó thành nội dung trái tim của họ. bản sao của danh sách, không phải danh sách.

Nhưng, tất nhiên, mang trên không đó là lý do tại sao tôi cảm thấy rằng nó chỉ ... tốt, nội bộ "sai" ở một mức độ nào đó, mặc dù nó hoạt động.

Danh sách tham số sẽ không bao giờ lớn. Tối đa nó sẽ chỉ chứa 50 mục, nhưng thường ít hơn mười mục, vì vậy tôi không thể thấy điều này bao giờ là một kẻ giết người hiệu suất. Tuy nhiên nó đã tất nhiên đặt tôi nghĩ rằng một ai đó, với giờ VB.NET nhiều hơn theo vành đai của họ, có thể có một ý tưởng gọn gàng hơn và sạch hơn.

Bất kỳ ai?

+1

Tôi nghĩ rằng giải pháp của riêng bạn đủ tốt và bạn không cần bất kỳ workaroung nào khác – SysDragon

+0

@SysDragon: dựa trên số lượng yếu tố trong bộ sưu tập, tôi đồng ý. – Paul

Trả lời

9

Thay vì tạo ra một bản sao mới của danh sách ban đầu, bạn nên sử dụng phương pháp AsReadOnly để ge t một phiên bản chỉ đọc của danh sách, như thế này:

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 

Theo MSDN:

Phương pháp này là một O (1) hoạt động.

Điều đó có nghĩa là tốc độ của phương pháp AsReadOnly giống nhau, bất kể kích thước của danh sách.

Ngoài các lợi ích hiệu năng tiềm năng, phiên bản chỉ đọc của danh sách sẽ tự động được đồng bộ với danh sách gốc, vì vậy nếu mã tiêu thụ giữ tham chiếu đến nó, danh sách được tham chiếu của nó sẽ vẫn được cập nhật ngày, ngay cả khi các mục sau đó được thêm vào hoặc bị xóa khỏi danh sách.

Ngoài ra, danh sách này thực sự là chỉ đọc. Nó không có phương thức Add hoặc Clear, do đó sẽ ít nhầm lẫn hơn đối với những người khác sử dụng đối tượng.

Ngoài ra, nếu bạn chỉ cần có cho người tiêu dùng để có thể lặp qua danh sách, sau đó bạn có thể chỉ tiếp xúc với tài sản như IEnumerable(Of String) đó là, vốn, một giao diện chỉ đọc:

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

Tuy nhiên , điều này chỉ hữu ích khi truy cập danh sách trong vòng lặp For Each. Ví dụ: bạn không thể lấy số Count hoặc truy cập các mục trong danh sách theo chỉ mục.

Lưu ý phụ, tôi khuyên bạn nên thêm một thuộc tính thứ hai Friend thay vì chỉ phơi bày trường, chính nó, dưới dạng Friend. Ví dụ:

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

Đó là BRILLIANT, cảm ơn bạn. Đó là một cách hiệu quả để có được nơi tôi cần phải đi. Tôi vẫn làm việc chủ yếu trong VBA và nhiều như MS làm phiền tôi ngày càng nhiều theo nhiều cách, tôi không thể không ấn tượng với một số thứ mà họ đã tích hợp vào .Net như cái này. Nó làm cho cuộc sống dễ dàng hơn nhiều so với Bộ sưu tập cũ clunky từ VB6. Tuy nhiên, các tính năng sẽ vô ích nếu không có những người biết cách áp dụng chúng theo cách này. Cảm ơn một lần nữa. –

1

gì về việc cung cấp một tài sản mà bạn có thể thiết lập Locked, mỗi tài sản khác sau đó kiểm tra này để xem nếu nó bị khóa ...

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 -

Chỉ cần để thêm vào này, sau đó, đây là một bộ sưu tập cơ bản ...

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

Ý tưởng rất thú vị ... mặc dù tôi nghĩ rằng khóa sẽ cần phải là bạn bè chứ không phải là phạm vi công cộng để ngăn chặn sửa đổi bên ngoài dự án. Vấn đề duy nhất với điều này là nó hạn chế việc gán bán buôn của một danh sách mới cho biến tham số, trong khi (và tôi có thể không đủ rõ ràng trong câu hỏi; tôi sẽ sửa đổi) các hành động bên trong chỉ đơn thuần sửa đổi danh sách, thông thường. Thêm hoặc. Xóa các phương thức. Tôi không bao giờ muốn/cần phải thay thế các đối tượng ban đầu với một đối tượng mới. Nhờ đề nghị mặc dù. –

+1

Ah - tôi hiểu; Tôi đã giải thích sai cụm từ của bạn * Bất kỳ lớp nào trong dự án truy cập biến mlst_ParameterNames đều có thể sửa đổi nó nếu cần *. Hmm. Cách duy nhất để kiểm soát điều này là cuộn bộ sưu tập của riêng bạn; theo cách đó bạn có thể kiểm soát mọi khía cạnh cuối cùng của những gì xảy ra. Tuy nhiên, đó có thể là một tình trạng hạt tạ và hạt. Tôi đồng ý với phạm vi 'Friend'. – Paul

Các vấn đề liên quan