c# - IEnumerable vs IReadonlyCollection vs ReadonlyCollection para exponer un miembro de la lista
.net encapsulation (3)
Hablando de bibliotecas de clases, creo que IReadOnly * es realmente útil, y creo que lo estás haciendo bien :)
Se trata de una colección inmutable ... Antes había solo inmutables y agrandar las matrices era una tarea enorme, por lo que .net decidió incluir en el marco algo diferente, la colección mutable, que implemente las cosas feas para ti, pero en mi humilde opinión no lo hicieron Te da una dirección adecuada para inmutables que son extremadamente útiles, especialmente en un escenario de concurrencia alta donde compartir cosas mutables siempre es un PITA.
Si revisas otros idiomas actuales, como objetivo-c, verás que, de hecho, ¡las reglas están completamente invertidas! Ellos siempre intercambian colecciones inmutables entre diferentes clases, en otras palabras, la interfaz es simplemente inmutable, e internamente usan colección mutable (sí, la tienen por supuesto), en su lugar exponen los métodos adecuados si quieren que los de afuera cambien la colección ( si la clase es una clase con estado).
Así que esta pequeña experiencia que tengo con otros idiomas me lleva a pensar que la lista .net es tan poderosa, pero la colección inmutable estaba allí por alguna razón :)
En este caso, no se trata de ayudar al interlocutor de una interfaz, de evitar que cambie todo el código si está cambiando la implementación interna, como ocurre con IList frente a List, pero con IReadOnly * está protegiéndose usted mismo, su clase, para ser utilizado de una manera no adecuada, para evitar el código de protección inútil, código que a veces no se puede escribir (en el pasado, en algún fragmento de código tuve que devolver un clon de la lista completa para evitar este problema) .
He pasado bastantes horas reflexionando sobre el tema de exponer a los miembros de la lista. En una pregunta similar a la mía, John Skeet dio una excelente respuesta. Por favor, siéntase libre de echar un vistazo.
¿ReadOnlyCollection o IEnumerable para exponer colecciones de miembros?
Normalmente soy bastante paranoico al exponer listas, especialmente si estás desarrollando una API.
Siempre he usado IEnumerable para exponer listas, ya que es bastante seguro y ofrece mucha flexibilidad. Déjame usar un ejemplo aquí
public class Activity
{
private readonly IList<WorkItem> workItems = new List<WorkItem>();
public string Name { get; set; }
public IEnumerable<WorkItem> WorkItems
{
get
{
return this.workItems;
}
}
public void AddWorkItem(WorkItem workItem)
{
this.workItems.Add(workItem);
}
}
Cualquiera que codifique contra un IEnumerable es bastante seguro aquí. Si luego decido usar una lista ordenada o algo, ninguno de sus códigos se rompe y sigue siendo bueno. La desventaja de esto es que IEnumerable se puede convertir a una lista fuera de esta clase.
Por esta razón, muchos desarrolladores usan ReadOnlyCollection para exponer a un miembro. Esto es bastante seguro ya que nunca se puede devolver a una lista. Para mí, prefiero IEnumerable ya que proporciona más flexibilidad, si alguna vez quisiera implementar algo diferente a una lista.
He encontrado una nueva idea que me gusta más. Usando IReadOnlyCollection
public class Activity
{
private readonly IList<WorkItem> workItems = new List<WorkItem>();
public string Name { get; set; }
public IReadOnlyCollection<WorkItem> WorkItems
{
get
{
return new ReadOnlyCollection<WorkItem>(this.workItems);
}
}
public void AddWorkItem(WorkItem workItem)
{
this.workItems.Add(workItem);
}
}
Siento que esto retiene algo de la flexibilidad de IEnumerable y está encapsulado bastante bien.
Publiqué esta pregunta para obtener información sobre mi idea. ¿Prefieres esta solución para IEnumerable? ¿Crees que es mejor usar un valor de retorno concreto de ReadOnlyCollection? Esto es todo un debate y quiero probar y ver cuáles son las ventajas / desventajas que todos podemos encontrar.
Gracias de antemano por sus comentarios.
EDITAR
Antes que nada, gracias a todos por contribuir tanto a la discusión aquí. Ciertamente he aprendido mucho de todos y me gustaría agradecerles sinceramente.
Estoy agregando algunos escenarios e información adicionales.
Hay algunos inconvenientes comunes con IReadOnlyCollection e IEnumerable.
Considera el siguiente ejemplo:
public IReadOnlyCollection<WorkItem> WorkItems
{
get
{
return this.workItems;
}
}
El ejemplo anterior se puede devolver a una lista y mutar, incluso si la interfaz es de solo lectura. La interfaz, a pesar de su nombre, no daña la inmutabilidad. Depende de usted proporcionar una solución inmutable, por lo tanto, debe devolver una nueva ReadOnlyCollection. Al crear una nueva lista (una copia esencialmente), el estado de su objeto es seguro.
Richiban lo dice mejor en su comentario: la interfaz solo garantiza lo que algo puede hacer, no lo que no puede hacer
Vea abajo para un ejemplo.
public IEnumerable<WorkItem> WorkItems
{
get
{
return new List<WorkItem>(this.workItems);
}
}
Lo anterior puede ser fundido y mutado, pero tu objeto aún es inmutable.
Otra declaración fuera de la caja sería clases de colección. Considera lo siguiente:
public class Bar : IEnumerable<string>
{
private List<string> foo;
public Bar()
{
this.foo = new List<string> { "123", "456" };
}
public IEnumerator<string> GetEnumerator()
{
return this.foo.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
La clase anterior puede tener métodos para mutar foo de la forma que desees, pero tu objeto nunca puede ser convertido en una lista de ningún tipo y mutado.
Carsten Führmann hace un punto fantástico sobre declaraciones de rendimiento en IEnumerables.
Gracias a todos de nuevo.
Parece que falta un aspecto importante en las respuestas hasta el momento:
Cuando se IEnumerable<T>
un IEnumerable<T>
a la persona que llama, deben considerar la posibilidad de que el objeto devuelto sea una "secuencia diferida", por ejemplo, una colección compilada con "rentabilidad de rendimiento". Es decir, es posible que la persona que realiza la llamada deba pagar la penalización de rendimiento por producir los elementos del IEnumerable<T>
para cada uso del IEnumerable. (La herramienta de productividad "Resharper" realmente señala esto como un olor a código).
Por el contrario, un IReadOnlyCollection<T>
indica al llamante que no habrá una evaluación diferida. (La propiedad Count
, a diferencia del método Count
de IReadOnlyCollection, indica que no hay pereza. Y también lo hace el hecho de que no parece haber implementaciones perezosas de IReadOnlyCollection).
Esto también es válido para los parámetros de entrada, ya que solicita una IReadOnlyCollection<T>
lugar de IEnumerable<T>
que el método necesita para iterar varias veces sobre la colección. Seguro que el método podría crear su propia lista a partir de IEnumerable<T>
e iterar sobre eso, pero como la persona que llama ya puede tener una colección cargada a la mano, tendría sentido aprovecharla siempre que sea posible. Si la persona que llama solo tiene un IEnumerable<T>
a mano, solo necesita agregar .ToArray()
o .ToList()
al parámetro.
Lo que IReadOnlyCollection no hace es evitar que la persona que llama copie a algún otro tipo de colección. Para tal protección, uno debería usar la clase ReadOnlyCollection<T>
.
En resumen, lo único que hace IReadOnlyCollection<T>
relación con IEnumerable<T>
es agregar una propiedad Count
y, por lo tanto, indicar que no se trata de pereza.
Parece que puede devolver una interfaz adecuada :
...
private readonly List<WorkItem> workItems = new List<WorkItem>();
// Usually, there''s no need the property to be virtual
public virtual IReadOnlyList<WorkItem> WorkItems {
get {
return workItems;
}
}
...
Dado que el campo workItems
es, de hecho, List<T>
la idea natural en mi humilde opinión es exponer la interfaz más amplia que es IReadOnlyList<T>
en el caso