118 votes

ReadOnlyCollection ou IEnumerable pour exposer les collections de membres ?

Y a-t-il une raison d'exposer une collection interne comme ReadOnlyCollection plutôt que comme IEnumerable si le code appelant ne fait qu'itérer sur la collection ?

class Bar
{
    private ICollection<Foo> foos;

    // Which one is to be preferred?
    public IEnumerable<Foo> Foos { ... }
    public ReadOnlyCollection<Foo> Foos { ... }
}

// Calling code:

foreach (var f in bar.Foos)
    DoSomething(f);

D'après moi, IEnumerable est un sous-ensemble de l'interface de ReadOnlyCollection et ne permet pas à l'utilisateur de modifier la collection. Donc, si l'interface IEnumberable est suffisante, c'est celle qu'il faut utiliser. Est-ce une bonne façon de raisonner ou est-ce que je rate quelque chose ?

Merci /Erik

93voto

Jon Skeet Points 692016

Une solution plus moderne

À moins que vous n'ayez besoin que la collection interne soit mutable, vous pouvez utiliser l'option System.Collections.Immutable modifiez votre type de champ pour qu'il devienne une collection immuable, puis exposez-le directement - en supposant que le paquet Foo elle-même est immuable, bien sûr.

Réponse actualisée pour répondre plus directement à la question

Y a-t-il une raison d'exposer une collection interne comme ReadOnlyCollection plutôt que comme IEnumerable si le code appelant ne fait qu'itérer sur la collection ?

Cela dépend de la confiance que vous accordez au code d'appel. Si vous avez un contrôle total sur tout ce qui appellera ce membre et que vous garantie qu'aucun code n'utilisera jamais :

ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);

alors bien sûr, aucun mal ne sera fait si vous renvoyez directement la collection. J'essaie généralement d'être un peu plus paranoïaque que ça.

De même, comme vous le dites : si seulement vous besoin de IEnumerable<T> alors pourquoi s'attacher à quelque chose de plus fort ?

Réponse originale

Si vous utilisez .NET 3.5, vous pouvez éviter de faire une copie. y éviter le simple cast en utilisant un simple appel à Skip :

public IEnumerable<Foo> Foos {
    get { return foos.Skip(0); }
}

(Il existe de nombreuses autres options pour envelopper de manière triviale - l'avantage de la fonction Skip par rapport à Select/Where est qu'il n'y a pas de délégué à exécuter inutilement à chaque itération).

Si vous n'utilisez pas .NET 3.5, vous pouvez écrire un wrapper très simple pour faire la même chose :

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}

41voto

Vojislav Stojkovic Points 4764

Si vous avez seulement besoin d'itérer dans la collection :

foreach (Foo f in bar.Foos)

puis en retournant IEnumerable est suffisant.

Si vous avez besoin d'un accès aléatoire à des éléments :

Foo f = bar.Foos[17];

puis l'envelopper dans Collection en lecture seule .

31voto

Stu Mackellar Points 8605

Si vous faites cela, rien n'empêche vos appelants de retranscrire IEnumerable en ICollection et de le modifier. ReadOnlyCollection supprime cette possibilité, bien qu'il soit toujours possible d'accéder à la collection sous-jacente inscriptible par réflexion. Si la collection est petite, un moyen sûr et facile de contourner ce problème est de retourner une copie à la place.

3voto

James Madison Points 49

J'évite autant que possible d'utiliser ReadOnlyCollection, c'est en fait beaucoup plus lent que d'utiliser une liste normale. Voir cet exemple :

List<int> intList = new List<int>();
        //Use a ReadOnlyCollection around the List
        System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList);

        for (int i = 0; i < 100000000; i++)
        {
            intList.Add(i);
        }
        long result = 0;

        //Use normal foreach on the ReadOnlyCollection
        TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks);
        foreach (int i in mValue)
            result += i;
        TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

        //use <list>.ForEach
        lStart = new TimeSpan(System.DateTime.Now.Ticks);
        result = 0;
        intList.ForEach(delegate(int i) { result += i; });
        lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

0voto

Il peut arriver que vous souhaitiez utiliser une interface, par exemple parce que vous voulez simuler la collection pendant les tests unitaires. Veuillez consulter mon entrée de blog pour ajouter votre propre interface à ReadonlyCollection en utilisant un adaptateur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X