2011-11-24 20 views
5

Tengo dos extensiones de IEnumerable:Extensiones para IEnumerable genérica

public static class IEnumerableGenericExtensions 
{ 
    public static IEnumerable<IEnumerable<T>> InSetsOf<T>(this IEnumerable<T> source, int max) 
    { 
     List<T> toReturn = new List<T>(max); 
     foreach (var item in source) 
     { 
      toReturn.Add(item); 
      if (toReturn.Count == max) 
      { 
       yield return toReturn; 
       toReturn = new List<T>(max); 
      } 
     } 
     if (toReturn.Any()) 
     { 
      yield return toReturn; 
     } 
    } 

    public static int IndexOf<T>(this IEnumerable<T> source, Predicate<T> searchPredicate) 
    { 
     int i = 0; 
     foreach (var item in source) 
      if (searchPredicate(item)) 
       return i; 
      else 
       i++; 

     return -1; 
    } 
} 

Entonces escribo este código:

 Pages = history.InSetsOf<Message>(500); 
     var index = Pages.IndexOf(x => x == Pages.ElementAt(0)); 

donde Historia clase pública: IEnumerable embargo, como resultado I' No obtuve '0' como esperaba, sino '-1'. No puedo entender, ¿por qué?

Respuesta

5

Cuando se escribe Pages.IndexOf(x => x == Pages.ElementAt(0));, en realidad se ejecuta InSetsOfmuchas veces, debido a deferred execution (también conocido como perezoso). Para ampliar:

  • Pages = history.InSetsOf<Message>(500) - esta línea no se ejecuta InSetsOf en absoluto.
  • Pages.IndexOf - Itera más de Pages, por lo que comienza a ejecutar InSetsOf una vez.
  • x == Pages.ElementAt(0) - esto ejecuta InSetsOf otra vez, una vez por cada elemento en la colección de Pages (o al menos hasta searchPredicate devuelve verdadero, lo que no sucede aquí).

Cada vez que se ejecuta InSetsOf se crea una nueva lista (en concreto, una nueva primera lista, porque utiliza ElementAt(0)). Estos son dos objetos diferentes, por lo que la comparación de == falla.

Una solución muy sencilla sería la de devolver una lista, por lo Pages no es una consulta diferida, sino una colección concreta:

Pages = history.InSetsOf<Message>(500).ToList(); 

Otra opción es utilizar SequenceEqual, aunque me gustaría recomendar el almacenamiento en caché la primera elemento de todos modos:

Pages = history.InSetsOf<Message>(500); 
var firstPage = Pages.FirstOrDefault(); 
var index = Pages.IndexOf(x => x.SequenceEqual(firstPage)); 
+0

¡Muchas gracias! Me olvidé por completo de esta peculiaridad de LINQ. – Seekeer

+0

@Seekeer - ¡No hay problema, feliz de ayudar! Este es un problema desagradable, es fácil cometer esos errores. La peor parte es cuando * no * ves un error, pero el código parece funcionar bien, pero de hecho es muy lento. (Aparte del error, hay una gran diferencia en la complejidad con o sin 'ToList' aquí) – Kobi

+0

Sí, estoy empezando a entender a las personas, a quienes no les gusta LINQ. Por cierto, ¿puedes recomendar algún buen artículo/libro sobre la eficacia y la optimización de las consultas LINQ? – Seekeer

1

¿Su clase T implementa el IComparable? De lo contrario, su comprobación de igualdad podría ser defectuosa, ya que el marco no sabe exactamente cuándo T = T. También lo haría sobrescribiendo a los iguales en su clase T, supongo.