2008-12-09

.NET BCL proposition: read-only collection interfaces

As a result of this thread on Channel 9, here's a proposition for a simple enhancement of the .NET BCL, related to collections.

If a class wants to expose a collection of items, the best thing to do is expose the collection as an abstract interface, so the internal class that is used can be changed without changing the API. If you want to expose a read-only collection, the options you have are IEnumerable<T>, ICollection<T> or IList<T>. IEnumerable<T> represents a read-only collection that can be enumerated, but you can't ask it how much elements it has. ICollection<T> and IList<T> represent collections that could be read-only, but this can only be checked at runtime and not defined at compile-time.

My proposition is to add 2 additional interfaces IReadOnlyCollection<T> and IReadOnlyList<T> that contain a subset of the members of ICollection<T> and IList<T> that can be performed on read-only collections. ICollection<T> could then implement IReadOnlyCollection<T> and IList<T> could implement IReadOnlyList<T> (and also ICollection<T>, like it does now). Adding these interfaces would not break existing code, they would be fully backward compatible and would not even require changes to the classes that implement ICollection<T> and IList<T>: they would automatically also implement the new interfaces (because ICollection<T> and IList<T> implement IReadOnlyCollection<T> and IReadOnlyList<T>).

The names of the new interfaces could also be ICollectionReader<T> and IListReader<T> or something similar. Here's how the interface hierarchy would look:

public class IEnumerable<T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

public class IReadOnlyCollection<T> : IEnumerable<T>
{
    int Count { get; }
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
}

public class IReadOnlyList<T> : IReadOnlyCollection<T>
{
    T this[int index] { get; }
    int IndexOf(T item);
}

public class ICollection<T> : IReadOnlyCollection<T>
{
    void Clear();
    void Add(T item);
    bool Remove(T item);
    bool IsReadOnly { get; }
}

public class IList<T> : ICollection<T>, IReadOnlyList<T>
{
    T this[int index] { get; set; }
    void Insert(int index, T item);
    void RemoveAt(int index);
}

Extending this to dictionaries:

public class IReadOnlyDictionary<TKey, TValue>
     : IReadOnlyCollection<KeyValuePair<TKey, TValue>>
{
    TValue this[TKey key] { get; }
    bool TryGetValue(TKey key, out TValue value);
    bool ContainsKey(TKey key);
    ICollection<TKey> Keys { get; }
    ICollection<TValue> Values { get; }
}

public class IDictionary<TKey, TValue>
     : ICollection<KeyValuePair<TKey, TValue>>, IReadOnlyDictionary<TKey, TValue>
{
    TValue this[TKey key] { get; set; }
    void Add(TKey key, TValue value);
    bool Remove(TKey key);
}
Technorati Tags: , , , .