ReadOnlyCollection ou IEnumerable pour exposer les collections de membres?

Y a-t-il une raison d’exposer une collection interne en tant que ReadOnlyCollection plutôt qu’un IEnumerable si le code d’appel ne parcourt que la collection?

class Bar { private ICollection foos; // Which one is to be preferred? public IEnumerable Foos { ... } public ReadOnlyCollection 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, alors 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

À moins que vous ayez besoin que la collection interne soit mutable, vous pouvez utiliser le package System.Collections.Immutable , changer votre type de champ pour qu’il soit une collection immuable, puis exposer cela directement – en supposant que Foo lui-même soit immuable, bien sûr.

Réponse mise à jour pour aborder la question plus directement

Y a-t-il une raison d’exposer une collection interne en tant que ReadOnlyCollection plutôt qu’un IEnumerable si le code d’appel ne parcourt que la collection?

Cela dépend de combien vous faites confiance au code d’appel. Si vous avez le contrôle total sur tout ce qui peut appeler ce membre et que vous garantissez qu’aucun code n’utilisera jamais:

 ICollection evil = (ICollection) bar.Foos; evil.Add(...); 

alors bien sûr, aucun mal ne sera fait si vous retournez 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 n’avez besoin que de IEnumerable , 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 dissortingbution simple en utilisant un simple appel à Ignorer:

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

(Il y a beaucoup d’autres options pour envelopper sortingvialement – la bonne chose à propos de Skip over Select / Where est qu’il n’y a pas de délégué à exécuter sans nécessité 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 Wrapper(IEnumerable source) { foreach (T element in source) { yield return element; } } 

Si vous devez seulement parcourir la collection:

 foreach (Foo f in bar.Foos) 

il suffit alors de retourner IEnumerable .

Si vous avez besoin d’un access aléatoire aux éléments:

 Foo f = bar.Foos[17]; 

puis enveloppez-le dans ReadOnlyCollection .

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

J’évite d’utiliser autant que possible ReadOnlyCollection, il est en réalité considérablement plus lent que l’utilisation d’une liste normale. Voir cet exemple:

 List intList = new List(); //Use a ReadOnlyCollection around the List System.Collections.ObjectModel.ReadOnlyCollection mValue = new System.Collections.ObjectModel.ReadOnlyCollection(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 .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).ToSsortingng()); MessageBox.Show("Result: " + result.ToSsortingng()); 

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