¿Cómo puedo devolver de forma segura la Lista del método / propiedad declarada como IEnumerable ?

Digamos que tengo la IEnumerable respaldada con el campo List , por lo que puedo modificar la colección desde dentro de la clase, pero está expuesta públicamente como de solo lectura.

 public class Foo { private List _bar = new List(); public IEnumerable Bar { get { return _bar; } } } 

Pero con un código como ese, puede convertir fácilmente el objeto recuperado de la propiedad a la List y modificarlo:

 var foo = new Foo(); var bar = (List)foo.Bar; bar.Add(10); 

La pregunta es: ¿cuál es la mejor manera de evitarlo (la mejor forma de leer, más fácil de escribir, sin pérdida de rendimiento)?

Puedo encontrar al menos 4 soluciones, pero ninguna de ellas es perfecta:

  1. foreach y yield return :

     public IEnumerable Bar { get { foreach (var item in _bar) yield return item; } } 

    Realmente molesto escribir y leer.

  2. AsReadOnly() :

     public IEnumerable Bar { get { return _bar.AsReadOnly(); } } 

    + causará una excepción cuando alguien intente modificar la colección devuelta
    + no crea una copia de toda la colección.

  3. ToList()

     public IEnumerable Bar { get { return _bar.ToList(); } } 

    + El usuario todavía puede modificar la colección recuperada, pero no es la misma colección que estamos modificando dentro de la clase, por lo que no debemos preocuparnos.
    crea una copia de toda la colección, lo que puede causar problemas cuando la colección es grande.

  4. Clase de envoltorio personalizado.

     public static class MyExtensions { private class MyEnumerable : IEnumerable { private ICollection _source; public MyEnumerable(ICollection source) { _source = source; } public IEnumerator GetEnumerator() { return _source.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_source).GetEnumerator(); } } public static IEnumerable AsMyEnumerable(this ICollection source) { return new MyEnumerable(source); } } 

    uso:

     public IEnumerable Bar { get { return _bar.AsMyEnumerable(); } } 

    + no es necesario clonar la colección
    cuando lo usa como fuente de consultas LINQ, algunos métodos no usarán ICollection.Count , porque no lo expone.


¿Hay alguna manera mejor de hacerlo?

La pregunta es: ¿cuál es la mejor manera de evitarlo (la mejor forma de leer, más fácil de escribir, sin pérdida de rendimiento)?

En general, no trato de evitarlo. El consumidor de mi API debe usar el tipo que expongo, y si no lo hace, cualquier error resultante es culpa suya, no mía. Como tal, realmente no me importa si emiten los datos de esa manera: cuando cambio mi representación interna, y obtengo excepciones, ese es su problema.

Dicho esto, si hay un problema de seguridad, probablemente solo usaría AsReadOnly . Esto es, efectivamente, autodocumentado y no tiene inconvenientes reales (aparte de una pequeña asignación para el contenedor, ya que no hay una copia de los datos, se obtienen excepciones significativas en la modificación, etc.). No hay una desventaja real en esto, en lugar de crear su propio envoltorio personalizado, y un envoltorio personalizado significa más código para probar y mantener.

En general, personalmente trato de evitar copiar sin motivo . Eso eliminaría ToList() como una opción en general. El uso de un iterador (su primera opción) no es tan malo, aunque en realidad no ofrece muchas ventajas sobre un ReadOnlyCollection .

En general estoy en el campamento de no molestar. Si te doy un IEnumerable y lo lanzas a otra cosa, tú eres el que rompió el contrato y no es mi culpa si algo se rompe. Si me encontrara en una posición en la que realmente necesitaba proteger a un cliente para que no corrompiera mi estado, crearía un método de extensión como:

 static IEnumerable AsSafeEnumerable(this IEnumerable obj) { foreach (var item in obj) { yield return item; } } 

y nunca te preocupes por eso otra vez.

Creo que ya ha explicado suficientemente muchas de las soluciones, pero una cosa que no vi que tomara en cuenta fue la sobrecarga de la asignación (sin embargo, el desempeño se mencionó como una preocupación). Las asignaciones son importantes y el hecho de que la propiedad asigne un nuevo objeto en cada llamada para la misma lista es un desperdicio. En su lugar, preferiría un objeto con una duración igual a la lista original que podría devolverse sin la sobrecarga adicional. Esencialmente una versión modificada de # 4

Estoy de acuerdo con la publicación de Reed Copsey, si el consumidor desea emitir y administrar los datos de una manera que corrompa los datos de la clase y luego los deje.

Puede pasar mucho tiempo tratando de “detener” una acción en particular para que ocurra solo para que un Reflejo inteligente arruine toda su palabra difícil.

Ahora para responder la pregunta, si tuvieras que hacer esto. La primera opción me parece la más relevante, sin embargo, no la enumeraré en la lista al llamar a la propiedad. Me gustaría devolver la colección interior como.

 public IEnumerable Bar { get { return _bar; } }