2008-12-19 19 views
28

¿Hay algún problema con el uso de IEnumerable<T> como tipo de devolución? FxCop se queja de devolver List<T> (en su lugar, recomienda devolver Collection<T>).IEnumerable <T> como tipo de retorno

Bueno, siempre me he guiado por la regla "acepta lo menos que puedas, pero devuelve el máximo".

Desde este punto de vista, devolver IEnumerable<T> es algo malo, pero ¿qué debo hacer cuando quiero usar "recuperación diferida"? Además, la palabra clave yield es una gran ventaja.

Respuesta

27

Esto es realmente una pregunta de dos partes.

1) ¿Hay algo malo intrínsecamente con la devolución de un IEnumerable <T>

Sin nada en absoluto. De hecho, si está utilizando iteradores de C#, este es el comportamiento esperado. Convertirlo a una lista <T> u otra clase de colección de manera preventiva no es una buena idea. Hacerlo es hacer una suposición sobre el patrón de uso de la persona que llama. Encuentro que no es una buena idea suponer nada sobre la persona que llama. Pueden tener buenas razones por las que desean un IEnumerable <T>. Quizás quieran convertirlo a una jerarquía de colecciones completamente diferente (en cuyo caso se pierde una conversión a List).

2) ¿Hay circunstancias en las que puede ser preferible a un resultado distinto del IEnumerable <T>?

Sí. Si bien no es una gran idea suponer demasiado acerca de las personas que llaman, está perfectamente bien tomar decisiones basadas en su propio comportamiento. Imagine un escenario en el que tiene un objeto de subprocesos múltiples que estaba haciendo cola solicitudes en un objeto que se actualiza constantemente.En este caso, devolver un IEnumerable sin procesar <T> es irresponsable. Tan pronto como se modifique la colección, el enumerable se invalida y provocará que ocurra una excepción. En su lugar, podría tomar una instantánea de la estructura y devolver ese valor. Diga en una lista <T> forma. En este caso, simplemente devolvería el objeto como la estructura directa (o interfaz).

Este es ciertamente el caso más raro sin embargo.

+0

Como indica el OP, si es necesario devolver una clase concreta, FxCop recomienda devolver una 'Colección ' en lugar de una 'Lista '. Consulte también [Colección frente a la lista ¿qué debería usar en sus interfaces?] (http://stackoverflow.com/q/271710/1497596). – DavidRR

27

No, IEnumerable<T> es buena Lo que debe devolver aquí, ya que todo lo que promete es "una secuencia de valores (mecanografiados)". Ideal para LINQ, etc., y perfectamente utilizable.

La persona que llama puede poner fácilmente estos datos en una lista (o lo que sea), especialmente con LINQ (ToList, ToArray, etc.).

Este enfoque le permite relajar los valores de forma lenta, en lugar de tener que almacenar todos los datos. Definitivamente un regalo. Escribí another useful IEnumerable<T> trick el otro día, también.

+1

¿Alguna idea de por qué fxcop se queja? – annakata

+0

¿Se queja de devolver IEnumerable ? Si es así, tómalo con un poco de sal ... no siempre es correcto. –

+3

Si le pide que piense en lo que * realmente * desea devolver, fxcop ha hecho su trabajo ;-p –

4

IEnumerable está bien para mí, pero tiene algunos inconvenientes. El cliente debe enumerar para obtener los resultados. No tiene forma de verificar el conteo, etc. La lista es incorrecta porque expone a mucho control; el cliente puede agregar/eliminar, etc. y eso puede ser algo malo. La colección parece el mejor compromiso, al menos en la vista de FxCop. Siempre utilizo lo que parece apropiado en mi contexto (por ejemplo, si deseo devolver una colección de solo lectura, expongo la colección como tipo de devolución y devuelvo List.AsReadOnly() o IEnumerable para evaluación diferida mediante rendimiento, etc.). Tómelo caso por caso

+1

El cliente puede usar ToList(), ToArray(), Count() o lo que quiera. –

+1

pero esos métodos crean nuevos objetos. cualquier modificación realizada no refleja el objeto de la lista original –

+0

¡Buen punto! caso por caso parece mejor. Aunque prefiero el enfoque IEnumerable, favorece la inmutabilidad. –

1

Solo porque diga que está regresando IEnumerable no significa que no puede devolver una Lista. La idea es reducir el acoplamiento innecesario. Todo lo que debe interesar a la persona que llama es obtener una lista de cosas, en lugar del tipo exacto de colección que se usa para contener esa lista. Si tiene algo respaldado por una matriz, obtener algo como Count va a ser rápido de todos modos.

+3

Si la persona que llama espera una * list *, entonces IList [] sería una buena opción. –

0

Creo que su propia orientación es excelente: si puede ser más específico acerca de lo que está devolviendo sin un golpe de rendimiento (no tiene que, por ejemplo, crear una lista de su resultado), hágalo . Pero si su función legítimamente no sabe qué tipo va a encontrar, por ejemplo, si en algunas situaciones estará trabajando con una lista y en algunas con una matriz, etc., devolver IEnumerable es lo "mejor" que puede hacer . Piense en ello como el "mayor múltiplo común" de todo lo que desee devolver.

2

Devolución IEnumerable <T> está bien si realmente solo devuelve una enumeración, y será consumida por la persona que llama como tal.

Pero a medida que otros señalan, tiene el inconveniente de que la persona que llama puede necesitar para enumerar si necesita cualquier otra información (por ejemplo, conde). El método .NET 3.5 extensión IEnumerable <T> .Count enumerará detrás de las escenas si el valor de retorno no implementa ICollection <T>, que puede ser indeseable.

A menudo vuelvo IList <T> o ICollection <T> cuando el resultado es una colección - internamente el método puede utilizar una lista <T> y, o bien devolverlo tal cual, o devolver Lista <T> .AsReadOnly si desea protegerse contra modificaciones (por ejemplo, si está almacenando en caché la lista internamente). AFAIK FxCop está bastante contento con cualquiera de estos.

3

Acerca de su principio: "aceptar lo menos que pueda, pero volver al máximo".

La clave para gestionar la complejidad de un programa grande es una técnica llamada información que oculta. Si su método funciona construyendo un List<T>, a menudo no es necesario revelar este hecho al devolver ese tipo. Si lo haces, tus interlocutores pueden modificar la lista que reciben. Esto elimina la capacidad de almacenar en caché o la iteración diferida con yield return.

Así que un principio es mejor para una función a seguir es: "revelar lo menos posible sobre la forma de trabajar".

2

Un aspecto importante es que cuando devuelve un List<T>, usted está actualizando una referencia . Eso hace posible que una persona que llama manipule su lista. Este es un problema común, por ejemplo, una capa empresarial que devuelve List<T> a una capa de GUI.

3

"aceptar lo menos que pueda, pero devuelve la máxima" es lo que defiendo. Cuando un método devuelve un objeto, qué justificaciones tenemos para no devolver el tipo real y limitar las capacidades del objeto devolviendo un tipo base. Sin embargo, esto plantea una pregunta sobre cómo sabemos cuál será el "máximo" (tipo real) cuando diseñemos una interfaz. La respuesta es muy simple. Solo en casos extremos en los que el diseñador de la interfaz está diseñando una interfaz abierta, que se implementará fuera de la aplicación/componente, no sabría cuál puede ser el tipo de devolución real. Un diseñador inteligente siempre debe considerar qué debería hacer el método y cuál debería ser el tipo de retorno óptimo/genérico.

E.g. Si estoy diseñando una interfaz para recuperar un vector de objetos, y sé que la cantidad de objetos devueltos va a ser variable, siempre asumiré que un desarrollador inteligente siempre usará una Lista. Si alguien planea devolver un Array, cuestionaría sus capacidades, a menos que él/ella solo esté devolviendo los datos de otra capa que él/ella no posee. Y esta es probablemente la razón por la que FxCop aboga por ICollection (base común para List y Array).

Lo anterior se dice, hay un par de cosas a tener en cuenta

  • si los datos devueltos deben ser mutable o inmutable

  • si los datos devueltos pueden compartir entre varias personas que llaman

En cuanto a las evaluaciones perezosas de LINQ, estoy seguro de que el 95% de los usuarios de C# no entienden las intestatudes. Es tan no-oo-ish. OO promueve cambios de estado concretos en las invocaciones de métodos. La evaluación diferida de LINQ promueve cambios de estado de tiempo de ejecución en el patrón de evaluación de expresiones (no es algo que los usuarios no avanzados siempre siguen).

0

No puedo aceptar la respuesta elegida. Hay formas de lidiar con el escenario descrito pero usando una lista o cualquier otra cosa que su uso no sea una de ellas. En el momento en que se devuelve IEnumerable, debe suponer que la persona que llama podría hacer un foreach. En ese caso, no importa si el tipo concreto es List o spaghetti. De hecho, solo la indexación es un problema, especialmente si se eliminan los elementos.

Cualquier valor devuelto es una instantánea. Puede ser el contenido actual de IEnumerable, en cuyo caso si está en caché, debe ser un clon de la copia en caché; si se supone que es más dinámico (como los resultados de una consulta sql), entonces use yield return; sin embargo, permitir que el contenedor mute a voluntad y proporcionar métodos como Count e indexer es una receta para el desastre en un mundo multiproceso. Ni siquiera he entrado en la capacidad de la persona que llama para llamar a Agregar o Eliminar en un contenedor que se supone que su código tiene el control.

También devolver un tipo de concreto lo encierra en una implementación. Hoy internamente puede estar usando una lista. Mañana quizás se convierta en multiproceso y desee utilizar un contenedor seguro para subprocesos o una matriz o una cola o la colección Valores de un diccionario o el resultado de una consulta Linq. Si te encierras en un tipo de retorno concreto, entonces tienes que cambiar un montón de código o hacer una conversión antes de volver.

Cuestiones relacionadas