[Info] How should a class expose a collection?

Coordinator
Feb 10, 2015 at 2:38 PM
Edited May 14, 2016 at 7:21 PM

This article has been moved to GitHub!


Exposing collections is a task that developers have to implement quite often. A lot variations exist on how to expose collections. I believe that the following approach should be advantageous for most scenarios.

Example 1
public class AddressBook
{
    private readonly List<Contact> contacts;

    public AddressBook()
    {
        this.contacts = new List<Contact>();
    }

    public IReadOnlyList<Contact> Contacts { get { return contacts; } }

    public void AddContact(Contact contact)
    {
        contacts.Add(contact);
    }

    public void RemoveContact(Contact contact)
    {
        contacts.Remove(contact);
    }
}
  • Do use the interface IReadOnlyList<T> or IReadOnlyCollection<T> to expose a collection.
    • Use IReadOnlyList<T> if the item order is important and deterministic.
    • Use IReadOnlyCollection<T> if the item order is not important.
  • Do provide methods for manipulating the collection.
    • This way you keep the control about manipulating the collection.
    • The methods could do more than just calling the respective collection method. Example: In some scenarios Add could be responsible to set a unique name to the newly added item.
  • Avoid using the interface IEnumerable<T> to expose a collection.
    • This interface does not define any guarantee that multiple enumerations perform well. If the IEnumerable<T> represents a query then every enumeration execute the query again. Developers that get an instance of IEnumerable<T> do not know if it represents a collection or a query. Note: ReSharper has a code inspection rule for “Possible multiple enumeration of IEnumerable”.
Example 2
// This example shows how you should not to expose a collection!
public class AddressBook
{
    public AddressBook()
    {
        Contacts = new List<Contact>();
    }
        
    // Property should not expose the concrete collection type and avoid providing a public setter.
    public List<Contact> Contacts { get; set; }
}
  • Do not expose a collection with its concrete type.
    • If you need to replace the collection type then this is a breaking change. It might be necessary to improve the performance.
    • You lose the control about manipulating the collection. If external code gets the concrete collection type then it can manipulate the collection directly.
  • Avoid that external code can replace the collection instance from outside.
    • External code should not be responsible to choose the best collection type for the class.
Scenario: Threading

The IReadOnlyCollection interfaces indicates that code working with this instance cannot (or should not via casting) manipulate the collection. However, it does not indicate that the collection does not change anymore. With threading it is important that the collection does not change anymore because of possible race conditions. In this case have a look at the Immutable Collections which target this scenario.

Further readings