Externally visible collection property is writable

ID

vbnet.maintainability.collection_properties_should_be_read_only

Severity

low

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

low

Resource

Api Design

Language

VB.NET

Description

Reports a Public, Protected or Friend property whose type is a mutable collection - a generic List(Of T), Dictionary(Of K, V), HashSet(Of T), ObservableCollection(Of T), Queue/Stack, one of the IList/ICollection/IDictionary/ISet interfaces, or a non-generic ArrayList, Hashtable, SortedList or NameValueCollection - and that is not declared ReadOnly, so callers can assign a different collection instance to it. The form that initialises the collection in the declaration, with As New List(Of T)() or with = New List(Of T)(), is reported as well. Properties declared ReadOnly, properties that are not externally visible, and fields of the same collection types are not reported.

Rationale

A writable collection property exposes two different powers, and only one of them is ever wanted. Callers need to add and remove items, which the collection object itself already allows through a read-only property. The setter adds the power to swap the whole collection, and nothing good is reachable through it. Anything the type derived from the old instance is invalidated silently: an event handler attached to an ObservableCollection, a comparer chosen for a HashSet, a Dictionary created with a case-insensitive comparer - all replaced by whatever the caller happened to construct, so lookups start failing on casing or on ordering with no change in the type’s own code. A caller can also assign Nothing, and from then on every method of the type has to defend against a null collection or throw. The worst case is aliasing: the caller keeps its reference and continues to mutate the collection the object is now using, so two components share mutable state that neither’s code suggests is shared. Making the property ReadOnly and initialising the collection once at construction removes all of these, and costs callers nothing they legitimately needed.

The following code illustrates the pattern detected by this rule:

Private _lines As New List(Of OrderLine)()

' FLAGGED: Externally visible collection property is writable
Public Property Lines As List(Of OrderLine)

' FLAGGED: Externally visible collection property is writable
Public Property Totals As Dictionary(Of String, Decimal)

' FLAGGED: Externally visible collection property is writable
Friend Property LegacyCodes As ArrayList

' FLAGGED: Externally visible collection property is writable
Protected Overridable Property Watchers As ObservableCollection(Of Watcher)
    Get
        Return _watchers
    End Get
    Set(value As ObservableCollection(Of Watcher))
        _watchers = value
    End Set
End Property

' FLAGGED: Externally visible collection property is writable
Public Property Discounts As New List(Of Discount)()

' FLAGGED: Externally visible collection property is writable
Public Property Adjustments As Dictionary(Of String, Decimal) = New Dictionary(Of String, Decimal)()

Remediation

Declare the property ReadOnly and initialise the backing collection at construction, letting callers use Add, Remove and Clear on the instance the type owns. Where callers really do need to supply a whole set of items, offer a method such as ReplaceLines(items) that copies into the existing collection, so the type keeps its instance, its comparer and its event subscriptions. Expose IReadOnlyList(Of T) or ReadOnlyCollection(Of T) when the contents are not meant to be modified from outside at all.

' Before: a caller can swap the collection, or assign Nothing
Public Property Lines As List(Of OrderLine)

' After
Private ReadOnly _lines As New List(Of OrderLine)()

Public ReadOnly Property Lines As IList(Of OrderLine)
    Get
        Return _lines
    End Get
End Property

Public Sub ReplaceLines(items As IEnumerable(Of OrderLine))
    _lines.Clear()
    _lines.AddRange(items)
End Sub

Configuration

This detector does not need any configuration.