Searching for a read-only list


A while back, Eric Lippert talked about arrays being somewhat harmful.  The reason being, if you’re looking to create logical immutability, arrays aren’t it.  Array length is immutable, but its contents aren’t.  If you need to return an array, make sure you create a new instance every single time, or it might get modified underneath you.

Eric discusses in detail the issues with arrays, and all of his issues make sense.  The only problem is, there aren’t any good alternatives in the BCL for a read-only collection.

You can use IEnumerable, but all of its functionality comes from the LINQ query extensions.  Count(), which was Length for arrays, is O(n) for most cases.  It does exactly what you think it does, and loops through the entire list and increments a count.  It does some intelligent checking, and returns the Count property for ICollection implementations, but for all other cases, just loops through the list.

IEnumerable also does not provide an indexer, so that really won’t work.

How about the next on the list, ICollection?  Here’s that interface:

public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    // Methods
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);

    // Properties
    int Count { get; }
    bool IsReadOnly { get; }
}

Hmmm, not quite.  There is an Add method, but no indexer.  And a funny flag, “IsReadOnly”, that users of the collection are supposed to check?  That’s not a very good user experience.  Also not good, as implementers of ICollection are expected to throw a NotSupportedException if IsReadOnly is false, for mutating operations like Add and Clear.

Blech, another violation of the Interface Segregation Principle.  If I have a read-only list, why would I need to implement any read-only operations?

Looking further, we find a promising candidate: ReadOnlyCollection.  However, this class merely wraps another list, which can be modified underneath.  Additionally, this class implements IList and IList, which means you could potentially get runtime errors because Add and Clear look like available operations.

The final option is an IReadOnlyList, of which there are a few options floating around the interwebs.  It would look something like this:

public interface IReadOnlyList<T> : IEnumerable<T>
{
    int IndexOf(T item);
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);

    int Count { get; }
    T this[int index] { get; }
} 

It’s not an ideal solution, but if you want immutable size and immutable contents, this kind of direction will be the way to go.

Testing fun with ASP.NET MVC