IEnumerable vs IReadonlyCollection vs ReadonlyCollection por expor um membro da lista

Passei algumas horas pensando no assunto de expor os membros da lista. Em uma pergunta semelhante à minha, John Skeet deu uma excelente resposta. Por favor, sinta-se livre para dar uma olhada.

ReadOnlyCollection ou IEnumerable para expor coleções de membros?

Geralmente sou bastante paranóico em expor listas, especialmente se você estiver desenvolvendo uma API.

Eu sempre usei o IEnumerable para expor listas, pois é bastante seguro e oferece muita flexibilidade. Deixe-me usar um exemplo aqui

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IEnumerable<WorkItem> WorkItems
    {
        get
        {
            return this.workItems;
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

Qualquer pessoa que codifique contra um IEnumerable é bastante segura aqui. Se eu decidir mais tarde usar uma lista ordenada ou algo assim, nenhum código será quebrado e ainda será bom. A desvantagem disso é IEnumerable, que pode ser convertida em uma lista fora desta classe.

Por esse motivo, muitos desenvolvedores usam o ReadOnlyCollection para expor um membro. Isso é bastante seguro, pois nunca pode ser lançado de volta para uma lista. Para mim, eu prefiro o IEnumerable, pois fornece mais flexibilidade, caso eu queira implementar algo diferente de uma lista.

Eu vim com uma nova idéia que eu gosto mais. Usando IReadOnlyCollection

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IReadOnlyCollection<WorkItem> WorkItems
    {
        get
        {
            return new ReadOnlyCollection<WorkItem>(this.workItems);
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

Eu sinto que isso retém parte da flexibilidade do IEnumerable e é bastante encapsulado.

Publiquei esta pergunta para obter alguma contribuição sobre minha ideia. Você prefere esta solução ao IEnumerable? Você acha que é melhor usar um valor de retorno concreto de ReadOnlyCollection? Este é um debate e quero tentar ver quais são as vantagens / desvantagens que todos nós podemos apresentar.

Agradecemos antecipadamente a sua contribuição.

EDITAR

Antes de tudo, obrigado a todos por contribuírem muito para a discussão aqui. Eu certamente aprendi muito com cada um e gostaria de agradecer sinceramente.

Estou adicionando alguns cenários e informações extras.

Existem algumas armadilhas comuns com IReadOnlyCollection e IEnumerable.

Considere o exemplo abaixo:

public IReadOnlyCollection<WorkItem> WorkItems
{
    get
    {
        return this.workItems;
    }
}

O exemplo acima pode ser convertido de volta para uma lista e alterado, mesmo que a interface seja somente leitura. A interface, apesar de ser homônima, não garante a imutabilidade. Cabe a você fornecer uma solução imutável; portanto, você deve retornar um novo ReadOnlyCollection. Ao criar uma nova lista (essencialmente uma cópia), o estado do seu objeto é seguro.

Richiban diz o melhor em seu comentário: a interface garante apenas o que algo pode fazer, não o que não pode fazer

Veja abaixo um exemplo.

public IEnumerable<WorkItem> WorkItems
{
    get
    {
        return new List<WorkItem>(this.workItems);
    }
}

O acima pode ser convertido e alterado, mas seu objeto ainda é imutável.

Outra declaração fora da caixa seria classes de coleção. Considere o seguinte:

public class Bar : IEnumerable<string>
{
    private List<string> foo;

    public Bar()
    {
        this.foo = new List<string> { "123", "456" };
    }

    public IEnumerator<string> GetEnumerator()
    {
        return this.foo.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

A classe acima pode ter métodos para mudar o idioma da maneira que você deseja, mas seu objeto nunca pode ser convertido para uma lista de qualquer tipo e alterado.

Carsten Führmann faz uma observação fantástica sobre as declarações de retorno de rendimento no IEnumerables.

Obrigado a todos mais uma vez.

questionAnswers(3)

yourAnswerToTheQuestion