Question

Existe-t-il une raison quelconque d'exposer une collection interne en tant que ReadOnlyCollection plutôt qu'en tant que IEnumerable si le code d'appel itère uniquement 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);

Comme je le vois, 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-là à utiliser. Est-ce une bonne façon de raisonner ou est-ce que je manque quelque chose?

Merci / Erik

Était-ce utile?

La solution

Une solution plus moderne

Sauf si vous souhaitez que la collection interne soit modifiable, vous pouvez utiliser le System.Collections.Immutable , modifiez votre type de champ pour qu'il devienne une collection immuable, puis exposez-le directement - en supposant que Foo lui-même est immuable, bien sûr.

Réponse mise à jour pour répondre plus directement à la question

  

Existe-t-il une raison quelconque d'exposer une collection interne en tant que ReadOnlyCollection plutôt qu'en tant que IEnumerable si le code d'appel itère uniquement sur la collection?

Cela dépend du degré de confiance que vous accordez au code d'appel. Si vous contrôlez totalement tout ce qui appellera ce membre et que vous garantissez qu'aucun code ne sera utilisé:

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

Ensuite, aucun dommage ne sera causé si vous retournez directement la collection. J'essaie généralement d'être un peu plus paranoïaque que ça.

De même, comme vous dites: si vous n’avez que besoin IEnumerable<T>, pourquoi vous attacher à quelque chose de plus fort?

Réponse originale

Si vous utilisez .NET 3.5, vous pouvez éviter de faire une copie et d'éviter la distribution simple en utilisant un simple appel à Ignorer:

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

(Il existe de nombreuses autres options pour effectuer un wrapping trivial - la bonne chose à propos de Skip sur Select / Where est qu'il n'y a aucun 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;
    }
}

Autres conseils

Si vous devez uniquement parcourir la collection:

foreach (Foo f in bar.Foos)

puis renvoyer IEnumerable est suffisant.

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

Foo f = bar.Foos[17];

puis enroulez-le dans ReadOnlyCollection .

Si vous faites cela, rien n'empêchera vos appelants de renvoyer IEnumerable à ICollection, puis de le modifier. ReadOnlyCollection supprime cette possibilité, même s'il est toujours possible d'accéder à la collection inscriptible sous-jacente via la réflexion. Si la collection est petite, un moyen simple et sûr de résoudre ce problème consiste à renvoyer une copie à la place.

J'évite autant que possible d'utiliser ReadOnlyCollection, il est en fait considérablement plus lent que la simple utilisation d'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());

Parfois, vous pouvez utiliser une interface, peut-être parce que vous souhaitez simuler la collection lors des tests unitaires. Veuillez consulter mon entrée de blog pour ajouter votre propre interface à ReadonlyCollection à l'aide de un adaptateur.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top