2012-02-09 11 views
6

IEnumerable no garantiza que enumerar dos veces arroje el mismo resultado. De hecho, es bastante fácil de crear un ejemplo en el myEnumerable.First() devuelve valores diferentes cuando se ejecuta dos veces:¿Existe alguna manera canónica de "arreglar" un IEnumerable "dinámico"?

class A { 
    public A(string value) { Value = value; } 
    public string Value {get; set; } 
} 

static IEnumerable<A> getFixedIEnumerable() { 
    return new A[] { new A("Hello"), new A("World") }; 
} 

static IEnumerable<A> getDynamicIEnumerable() { 
    yield return new A("Hello"); 
    yield return new A("World"); 
} 

static void Main(string[] args) 
{ 
    IEnumerable<A> fix = getFixedIEnumerable(); 
    IEnumerable<A> dyn = getDynamicIEnumerable(); 

    Console.WriteLine(fix.First() == fix.First()); // true 
    Console.WriteLine(dyn.First() == dyn.First()); // false 
} 

Esto no es sólo un ejemplo académico: El uso de la popular from ... in ... select new A(...) creará exactamente esta situación. Esto puede conducir a un comportamiento inesperado:

fix.First().Value = "NEW"; 
Console.WriteLine(fix.First().Value); // prints NEW 

dyn.First().Value = "NEW"; 
Console.WriteLine(dyn.First().Value); // prints Hello 

entiendo por qué sucede esto. también sé que esto podría ser fijado mediante la ejecución de ToList() en el Enumerable o por razones imperiosas == para la clase A. Esa no es mi pregunta.

La pregunta es: Cuando escribe un método que toma un IEnumerable arbitrario y desea la propiedad de que la secuencia solo se evalúa una vez (y luego las referencias son "correctas"), ¿cuál es la manera canónica de hacerlo? ToList() parece que se usa principalmente, pero si la fuente ya está fijada (por ejemplo, si la fuente es una matriz), las referencias se copian a una lista (innecesariamente, ya que todo lo que necesito es la propiedad fija). ¿Hay algo más adecuado o es ToList() la solución "canónica" para este problema?

+1

Por definición, ¿un IEnumerable no dice que el orden no está garantizado? Si se requiere orden para su implementación, está utilizando el tipo de datos incorrecto, ¿no? Todo lo que pides en tu contrato con IEnumerable es que los elementos se pueden recorrer IE: Como dices, una lista es más adecuada. –

Respuesta

3

ToList es definitivamente el camino a seguir.

Tiene algunas optimizaciones: si la enumeración de entrada es una ICollection (por ejemplo, una matriz o lista) se llamará a ICollection.CopyTo copiar la colección a una matriz en lugar de realmente enumerar - por lo que el costo es poco probable que sea significativo a menos que la colección es enorme

en mi humilde opinión, en general, es mejor para la mayoría de los métodos para volver ICollection<T> o IList<T> en lugar de IEnumerable<T>, menos desea hacer alusión a los consumidores del método que la aplicación puede utilizar la evaluación perezosa (por ejemplo, rendimiento).

En los casos en que un método debe devolver una lista inmutable, devuelva un contenedor de solo lectura (ReadOnlyCollection<T>) p. llamando al ToList().AsReadOnly(), pero aún devuelve la interfaz tipo IList<T>.

Si sigue esta guía, los consumidores del método no necesitarán una llamada innecesaria al ToList.

2

Quieres decir si tu función toma un parámetro y quieres asegurarte de que el parámetro no sea flojo, porque necesitas iterar sobre él más de una vez en el método? Por lo general, hago que el parámetro sea ICollection en su lugar, por lo que es responsabilidad del llamante reificar el enumerable si es flojo.

0

Por la forma en que ha explicado su pregunta, parece que desea enumerar una secuencia varias veces y obtener el mismo orden cada vez. Esto implica que o bien mantiene una referencia a la secuencia para realizar iteraciones más tarde, o bien está haciendo comparaciones varias veces dentro del mismo método.

En general, al aceptar un IEnumerable<T> que quiere mantener una referencia o manipular más de una vez, es más seguro copiar la secuencia a una representación interna. Mi preferencia es usar la extensión ToList(), ya que es muy conocida y posiblemente la colección más simple con un orden predecible.

Internamente, .NET Framework tiende a usar List<T> siempre que se requiera una recopilación "general".

1

El problema más común lo afirma Joel Spolsky en su Law of Leaky Abstractions. Al tener el parámetro IEnumerable en su método, espera un objeto que solo se puede enumerar y nada más. Sin embargo, sí importa la colección pasada, ya sea "fija" o "dinámica". Puede ver que la abstracción hecha por IEnumerable se filtró. No hay una solución que se adapte bien a todos los casos. En su caso, puede pasar List<T> o T[] (o algún otro tipo que espera que sean tipos reales para los parámetros en su método) en lugar de IEnumerable. El consejo más común es realizar su abstracción y diseñar su código con respecto a ella.

2

La interfaz IEnumerable simplemente da "una manera" de iterar a través de los elementos (la creación de un IEnumerable de una consulta linq es un ejemplo perfecto ya que IEnumerable es como una consulta SQL para una base de datos). Siempre será dinámico hasta que almacene el resultado en ICollection (ToList, ToArray), por lo que la iteración se procesa hasta el final y el resultado se almacena de forma "fija".

Cuestiones relacionadas