Y a-t-il une raison d'exposer une collection interne en tant que ReadOnlyCollection plutôt que 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);
Comme je le vois, IEnumerable est un sous-ensemble de l'interface de ReadOnlyCollection et il ne permet pas à l'utilisateur de modifier la collection. Donc, si l'interface IEnumberable est suffisante, c'est celle à utiliser. Est-ce une bonne façon de raisonner ou est-ce que je manque quelque chose?
Merci/Erik
Solution plus moderne
Sauf si vous avez besoin que la collection interne soit modifiable, vous pouvez utiliser System.Collections.Immutable
package, changez votre type de champ pour être une collection immuable, puis exposez cela directement - en supposant que Foo
lui-même est immuable, bien sûr.
Réponse mise à jour pour répondre plus directement à la question
Y a-t-il une raison d'exposer une collection interne en tant que ReadOnlyCollection plutôt que IEnumerable si le code appelant ne fait qu'itérer sur la collection?
Cela dépend de la confiance que vous accordez au code appelant. Si vous contrôlez complètement tout ce qui appellera ce membre et que vous garantissez 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 retournez simplement la collection directement. J'essaie généralement d'être un peu plus paranoïaque que ça.
De même, comme vous le dites: si vous seulement besoinIEnumerable<T>
, alors 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 éviter la distribution simple en utilisant un simple appel à Skip:
public IEnumerable<Foo> Foos {
get { return foos.Skip(0); }
}
(Il existe de nombreuses autres options pour encapsuler trivialement - ce qui est bien avec Skip
sur Select/Where, c'est qu'il n'y a pas de délégué à exécuter inutilement pour 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;
}
}
Si vous avez seulement besoin de parcourir la collection:
foreach (Foo f in bar.Foos)
puis retourner IEnumerable suffit.
Si vous avez besoin d'un accès aléatoire aux articles:
Foo f = bar.Foos[17];
puis envelopper dans ReadOnlyCollection.
Si vous faites cela, rien n'empêche vos appelants de restituer l'IEnumerable à ICollection, puis de le modifier. ReadOnlyCollection supprime cette possibilité, bien qu'il soit toujours possible d'accéder à la collection inscriptible sous-jacente via la réflexion. Si la collection est petite, un moyen sûr et facile de contourner ce problème est de renvoyer une copie à la place.
J'évite d'utiliser ReadOnlyCollection autant que possible, c'est en fait considérablement plus lent que d'utiliser simplement 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 souhaiterez peut-être utiliser une interface, peut-être parce que vous souhaitez simuler la collection pendant les tests unitaires. Veuillez consulter mon entrée de blog pour ajouter votre propre interface à ReadonlyCollection en utilisant un adaptateur.