visual una programacion objetos metodos listas lista guardar genericos generica datos coleccion c# .net collections ienumerable readonlycollection

c# - una - ¿ReadOnlyCollection o IEnumerable para exponer colecciones de miembros?



una lista de objetos (5)

¿Hay alguna razón para exponer una colección interna como ReadOnlyCollection en lugar de IEnumerable si el código de la llamada solo itera sobre la colección?

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);

Como lo veo, IEnumerable es un subconjunto de la interfaz de ReadOnlyCollection y no permite al usuario modificar la colección. Entonces, si la interfaz IEnumberable es suficiente, entonces esa es la que se debe usar. ¿Es esa una forma adecuada de razonar al respecto o me falta algo?

Gracias / Erik


A veces es posible que desee utilizar una interfaz, tal vez porque quiere burlarse de la colección durante la prueba unitaria. Consulte la entrada de mi blog para agregar su propia interfaz a ReadonlyCollection utilizando un adaptador.


Evito utilizar ReadOnlyCollection tanto como sea posible, en realidad es considerablemente más lento que el simple uso de una lista normal. Mira este ejemplo:

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());


Si haces esto, entonces no hay nada que impida que tus interlocutores vuelvan a lanzar IEnumerable a ICollection y luego lo modifiquen. ReadOnlyCollection elimina esta posibilidad, aunque todavía es posible acceder a la colección editable subyacente a través de la reflexión. Si la colección es pequeña, entonces una forma segura y fácil de evitar este problema es devolver una copia.


Si solo necesita iterar a través de la colección:

foreach (Foo f in bar.Foos)

luego, regresar IEnumerable es suficiente.

Si necesita acceso aleatorio a los artículos:

Foo f = bar.Foos[17];

luego envuélvalo en ReadOnlyCollection .


Una solución más moderna

A menos que necesite que la colección interna sea mutable, podría usar el paquete System.Collections.Immutable , cambiar su tipo de campo para que sea una colección inmutable y luego exponerlo directamente, suponiendo que Foo sea ​​inmutable, por supuesto.

Respuesta actualizada para abordar la pregunta más directamente

¿Hay alguna razón para exponer una colección interna como ReadOnlyCollection en lugar de IEnumerable si el código de la llamada solo itera sobre la colección?

Depende de cuánto confíe en el código de llamada. Si tiene el control total de todo lo que alguna vez llamará a este miembro y le garantiza que ningún código lo usará nunca:

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

entonces seguro, no se dañará si solo devuelve la colección directamente. En general, trato de ser un poco más paranoico que eso.

Del mismo modo, como dices: si solo necesitas IEnumerable<T> , ¿por qué te IEnumerable<T> a algo más fuerte?

Respuesta original

Si está usando .NET 3.5, puede evitar hacer una copia y evitar el simple reparto usando una simple llamada a Omitir:

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

(Hay muchas otras opciones para envolver trivialmente: lo bueno de Skip over Select / Where es que no hay ningún delegado para ejecutar inútilmente para cada iteración).

Si no está usando .NET 3.5, puede escribir un contenedor muy simple para hacer lo mismo:

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