2010-07-16 15 views
13

A menudo tengo el caso en el que deseo devolver un Enumerable<T> desde un método o una propiedad. Para compilar el Enumerable<T> que regresa, utilizo un List<T> -instance. Después de completar la lista, devuelvo la lista.¿Es un error devolver una lista si el tipo de devolución es enumerable?

Siempre pensé que esto era suficiente. Pero existe la posibilidad de que la persona que llama arroja el resultado Enumerable<T> de vuelta al List<T> y comienza a trabajar más con él. Si en un momento posterior cambio la implementación de mi método, el código de la persona que llama fallará. Para evitar esto, podría devolver list.ToArray o hacer una lista de solo lectura antes de devolverla a la persona que llama. Pero para mí esto parece ser una gran exageración. ¿Qué piensas?

Tenga en cuenta que Nunca devolveré una lista de uso interno para que la persona que llama pueda cambiar el estado interno de mis objetos. La pregunta es solo acerca de una lista viva corta que se construye temporalmente para contener los valores devueltos.

IEnumerable<string> GetAList() { 
    List<string> aList = new List<string>(); 
    aList.Add("a"); 
    aList.Add("b"); 
    return aList; 
} 

IEnumerable<string> GetAList() { 
    List<string> aList = new List<string>(); 
    aList.Add("a"); 
    aList.Add("b"); 
    return aList.ToArray<string>(); 
} 

Los ejemplos son super-sencilla y en este caso me gustaría trabajar desde el principio con las matrices, pero es sólo para mostrar a explicar la cuestión.

+0

También podrían usar métodos LINQ como '.Concat (...)' ... realmente no se puede evitar que otros desarrolladores se tomen fotos. –

+0

No creo que sea tan terrible exponer indirectamente el estado interno a través de retornos IEnumerable. El acceso privado es privado por convención; el compilador hace cumplir esto bastante bien, pero una vez que está en un ambiente de plena confianza con Reflection, todas las apuestas están desactivadas. De acuerdo, alguien que use Reflection para acceder a un campo privado debería tener una idea bastante clara del peligro de lo que están haciendo, pero es solo marginalmente peor que lanzar un tipo de devolución que no esté garantizado por el contrato de la clase. Idealmente, ni siquiera pueden averiguar qué tipo está devolviendo sin Reflector o el depurador. –

Respuesta

14

No, esto está bien.

Este es un ejemplo de 'polimorfismo' en el trabajo. Debido a que la persona que llama al método solo está interesada en un IEnumerable<string>, el funcionamiento interno del método es libre de devolver la clase que quiera, siempre que proceda de la interfaz IEnumerable<string>.

Si la persona que llama tiene la IEnumerable<string> y proyecta hasta entonces List<string> que han roto el contrato, que sólo indica que se devolverá un 'IEnumerable<string>.

+0

Y depende de la persona que llama no asumir algo que no sea la firma declara. ¿Derecha? – HCL

+0

Sí, solo estaba editando para agregar que, mientras comentábamos :) –

+0

He aceptado esta respuesta porque era la primera. Pero para todos los que estén interesados ​​en este tema, les recomiendo que lean todas las publicaciones y, sobre todo, los comentarios. Nunca he hecho una publicación que haya llevado a comentarios tan interesantes. – HCL

15

la posibilidad de que la persona que llama arroja el resultado Enumerable<T> de nuevo en el List<T> y comienza a trabajar más con él

Cualquier persona que llama que hace que sólo tiene a sí mismos la culpa si cambia su implementación. Usted se compromete a devolver un Enumerable, siempre y cuando continúe haciéndolo, no puede hacerse responsable de los problemas en las personas que llaman que suponen más que eso.

Tenga en cuenta también what @Chris mentions - que puede haber políticos cuestiones que en algún momento le obliguen a mantener la compatibilidad hacia atrás incluso para las personas que llaman 'rompieron las reglas' - Raymond Chen (que trabaja para Microsoft en el equipo de aplicación de compatibilidad) tiene un blog lleno de historias de las travesuras que resultan cuando 'esta aplicación se rompe en la versión del sistema operativo x + 1' no es una respuesta aceptable ...

2

Al devolver un IEnumerable<T> su contrato con los usuarios de su código indica que lo que sea regresas implementará IEnumerable<T> y nada más. Si alguien hace suposiciones basadas en eso, es una mala práctica de programación de su parte, así que no te preocupes por eso. Un List<T> construido localmente está bien en este escenario.

6

Se podría producir los elementos de la lista del interior de su método, evitando así cualquier tipo de comportamiento mal concebida por parte de la persona que llama:

IEnumerable<string> GetAList() { 
    List<string> aList = new List<string>(); 
    aList.Add("a"); 
    aList.Add("b"); 
    foreach (string s in aList) 
     yield return s; 
} 

Por supuesto, a menudo se puede simplemente dar valores a medida que los produce, evitando así la lista en su totalidad:

IEnumerable<string> GetAList() { 
    yield return "a"; 
    yield return "b"; 
} 
+0

Esto diría que crees que debo preocuparme por el uso indebido de la persona que llama o ¿es solo un ejemplo de cómo puedo traducir/proteger la lista? – HCL

+2

@HCL: Diría que, de muchas maneras, no es su problema, pero, por otro lado, si su código se puede refactorizar trivialmente para evitar abusos por parte de terceros, parece una buena idea. Después de todo, si cambias en el futuro y su código deja de funcionar por mucho que sea su culpa, ¿crees que se molestarán contigo por cambiarlo? Puede que no sea su responsabilidad, pero eso no significa que no será su problema. – Chris

+5

Usar el rendimiento de esta manera tiene un impacto que debe tener en cuenta. Por ejemplo, el método de extensión Enumerable.Count intenta un lanzamiento a ICollection con el operador as, y solo enumera si falla este molde. Además, si la persona que llama enumera el resultado más de una vez, entonces la totalidad de su método se ejecutará más de una vez si está utilizando el rendimiento: solo se ejecuta una vez si devuelve una colección. – Joe

3

Creo que su problema es inverosímil porque si alguien indebidamente utilizando sus métodos (haciendo hipótesis sobre la aplicación interna), luego en realidad ese no es su problema.

Pero si el uso de .NET 3.5, entonces se puede utilizar para ocultar por completo AsEnumerable implementación interna:

return aList.AsEnumerable(); 

O simplemente lista de envolver con un rendimiento

foreach (string NextStr in aList) 
    yield return NextStr; 
+0

'AsEnumerable' no funcionará, porque devolverá la misma instancia. – Steven

+1

'.Select (s => s)' funcionará. –

+0

@Steven, sí, tienes razón, acabo de comprobarlo con Reflector. – arbiter

1

Si cambia su posterior implementación en, y devuelve algo que no sea List<T>, el código de la persona que llama se romperá. Pero el autor del código de la persona que llama debe saber mejor que simplemente enviar contenido a List<T> sin verificar que el valor de retorno en realidad es una lista; no ha prometido nada de eso.

En cuanto a mí, tiendo a devolver theList.AsEnumerable(), solo para ser más claro, pero eso no es necesario. El código de la persona que llama no sabrá cualquier cosa sobre qué implementación de se devuelve - solo que se devuelve alguna implementación de.

4

Pensé que sería una respuesta completa en lugar de solo un comentario.

AS otros han dicho que si obedece el contrato y devuelve un ienumerable otros no deberían hacer más suposiciones que eso. Sin embargo, algunas personas podrían. Si luego lo cambias, inevitablemente vendrán y te culparán por haberlo interrumpido (si no entendieron lo suficiente como para codificarlo correctamente, es poco probable que descubran el error rápidamente).

Cuando esto sucede, tiene dos posibilidades. Les dices que es su problema y luego los dejas para arreglarlo o cambias tu código para apoyar el suyo. La solución correcta es la primera. Sin embargo, en los negocios esto podría no ser factible. Si es un cliente que sus jefes dicen que necesita para mantenerse feliz, es posible que le digan que su código debe cambiar para mantener al cliente contento y mantener su negocio.

Por lo tanto, desde este punto de vista, aunque no es su responsabilidad, puede convertirse en su problema.

Si su código es fácilmente refactorizable para devolver el tipo exacto prometido, entonces lo haría. Su trabajo potencialmente poco ahora para ahorrar mucho trabajo más tarde y, en teoría, nadie debería notar su cambio.

+0

Sí, este es un muy buen punto. – AakashM

+0

"Nadie debería notar su cambio". Bien, ¿y si otro usuario viene quejándose del hecho de que está lanzando a un tipo diferente que implementa IEnumerable pero no es una lista? ¿Quién debería estar satisfecho? No, estoy completamente de acuerdo con tu primer punto, y no con tu segundo. –

+0

@Tomas Lycken: Por "Nadie debería notar su cambio". Quiero decir que si todo el mundo se comporta y trata el tipo de devolución simplemente como un Enumerable, entonces nadie debería notar el cambio de la Lista a IEnumerable. – Chris

2

Expandiendo en un comentario hecho a Marcelo Cantos respuesta.

Algunas personas aquí están recomendando usar el rendimiento para garantizar que la persona que llama solo recupere la implementación mínima prometida por el contrato IEnumerable<T>.

Sin embargo, hay algunas desventajas potenciales a esto que deben tenerse en cuenta, así que no creo que debe ser recomendado como una solución manta en todos los casos.

Por ejemplo, consideremos el siguiente método de acceso de datos que utiliza el rendimiento en lugar de devolver una lista:

public IEnumerable<SomeType> YieldMethod(...) 
{ 
    using(IDbConnection connection =) 
    { 
     ... 
     using (IDataReader reader = ...) 
     { 
      while(reader.Read()) 
      { 
       SomeType someType = ... 
       ... 
       yield return someType; 
      } 
     } 
    } 
} 

Esto se comportará de manera diferente a una versión que devuelve una lista de las siguientes maneras, algunas de las cuales pueden ser indeseable:

  1. Si la persona que llama enumera el resultado más de una vez, entonces se accede a la base de datos y el resultado regenera cada vez que se enumera.

  2. Si la persona que llama utiliza el método de extensión Enumerable.Count, se accederá a la base de datos y el resultado se regenerará cada vez que se invoca.

  3. El cuerpo del método no se ejecutará hasta que la persona que llama en realidad enumera el resultado. Esto significa que no se lanzará ninguna excepción de acceso a datos hasta que la persona que llama comience a enumerar. Un caso donde esto puede ser problemático es si el resultado se devuelve directamente como resultado de un Servicio web de WCF; en este caso, no se lanzará ningún acceso de datos hasta que la infraestructura de WCF comience a serializar el resultado, de modo que el código de usuario en el el servicio no puede manejar la excepción.

+0

La moraleja de la historia es que un método que devuelva 'IEnumerable ' debe estar claramente documentado en cuanto a si su evaluación es diferida, particularmente si la enumeración es costosa o tiene efectos secundarios. –

+0

"debe estar claramente documentado en cuanto a si su evaluación se aplaza" ... y si no se difiere, el método probablemente también devuelva un ICollection o IList de todos modos. – Joe

Cuestiones relacionadas