2012-04-09 9 views
6

Si estoy tratando de filtrar los resultados en varios niveles de un gráfico de objetos IEnumerable<T>, ¿hay una forma preferida de encadenar métodos de extensión para hacer esto?¿Cuál es la forma preferida (rendimiento y legibilidad) de encadenar los métodos de extensión IEnumerable <T>?

Estoy abierto a cualquier método de extensión y uso de lambda, pero prefiero no usar la sintaxis LINQ para mantener la coherencia con el resto de la base de código.

¿Es mejor empujar el filtrado al selector del método SelectMany() o simplemente para encadenar otro método Where()? ¿O hay una mejor solución?

¿Cómo podría identificar la mejor opción? En este caso de prueba, todo está directamente disponible en la memoria. Obviamente, ambas muestras a continuación están produciendo los mismos resultados correctos; Solo busco una razón por la que una u otra (u otra opción) sería preferible.

public class Test 
{ 
    // I want the first chapter of a book that's exactly 42 pages, written by 
    // an author whose name is Adams, from a library in London. 
    public Chapter TestingIEnumerableTExtensionMethods() 
    { 
     List<Library> libraries = GetLibraries(); 

     Chapter chapter = libraries 
      .Where(lib => lib.City == "London") 
      .SelectMany(lib => lib.Books) 
      .Where(b => b.Author == "Adams") 
      .SelectMany(b => b.Chapters) 
      .First(c => c.NumberOfPages == 42); 

     Chapter chapter2 = libraries 
      .Where(lib => lib.City == "London") 
      .SelectMany(lib => lib.Books.Where(b => b.Author == "Adams")) 
      .SelectMany(b => b.Chapters.Where(c => c.NumberOfPages == 42)) 
      .First(); 
    } 

Y aquí está el gráfico de objetos de muestra:

public class Library 
{ 
    public string Name { get; set; } 
    public string City { get; set; } 
    public List<Book> Books { get; set; } 
} 

public class Book 
{ 
    public string Name { get; set; } 
    public string Author { get; set; } 
    public List<Chapter> Chapters { get; set; } 
} 

public class Chapter 
{ 
    public string Name { get; set; } 
    public int NumberOfPages { get; set; } 
} 
+1

La legibilidad parece ser casi igual. El rendimiento depende de si esto es linq para los objetos o linq para cualquier otra cosa; ¿Por qué no lo mides y ves? – phoog

+0

son sus consultas siempre en forma de 'libraries => books => capters' - o podría tener relaciones más complejas (supongo que esto es solo un modelo de prueba) – NSGaga

+0

@NSGaga Siempre están en esa forma jerárquica, pero tiene razón en que es solo un modelo de prueba para que los lectores lo entiendan mejor. Técnicamente, estoy trabajando con estructuras EDI (ANSI X12), por lo que los archivos, ISA, GS y segmentos ST. –

Respuesta

1

Depende de cómo funciona el proveedor LINQ subyacentes. Para LINQ to Objects, ambos en este caso requerirían aproximadamente la misma cantidad de trabajo, más o menos. Pero ese es el ejemplo más simple (más simple), así que más allá de eso es difícil de decir.

2

Supongo que la primera expresión que tendrá será ligera pero insignificantemente más rápida. Para determinar realmente si uno u otro es más rápido, tendrá que cronometrarlos, con un generador de perfiles o cronómetro.

La legibilidad no parece verse muy afectada de ninguna manera. Prefiero el primer enfoque, ya que tiene menos niveles de anidación. Todo depende de tus preferencias personales.

+0

Sí, el primero será más rápido porque no está creando un iterador de cada elemento. – usr

3

Lo más probable es que varíe en función de la implementación de LINQ que esté utilizando. LinqToSql se comportará de forma diferente al filtrado en memoria. El orden de las cláusulas debería afectar el rendimiento dependiendo de qué datos se usen, ya que las implementaciones ingenuas filtrarán más registros más temprano en la secuencia, lo que significa menos trabajo para los métodos posteriores.

Para sus dos ejemplos, supongo que la diferencia de rendimiento es insignificante y favorecería la primera, ya que permite una modificación más sencilla de cada cláusula, independientemente de las demás.

En cuanto a determinar la mejor opción, es lo mismo que cualquier otra cosa: medida.

0

Esto podría darle un ángulo diferente, aunque es más una cuestión de estilo ...
veces me encuentro haciendo algo como esto ...

return libraries.Filter(
     l => l.City == "", 
     l => l.Books, 
     b => b.Author == "Adams", 
     b => b.Chapters, 
     c => c.NumberOfPages == 42 
     ); 

... donde se puede adivinar lo la extensiion es, algo así como ...

public static IEnumerable<TC> Filter<TL, TB, TC>(this IEnumerable<TL> list, 
    Func<TL, bool> whereLs, 
    Func<TL, IEnumerable<TB>> selectBs, 
    Func<TB, bool> whereBs, 
    Func<TB, IEnumerable<TC>> selectCs, 
    Func<TC, bool> whereCs 
    ) 
{ 
    return list 
     .Where(whereLs) 
     .SelectMany(selectBs) 
     .Where(whereBs) 
     .SelectMany(selectCs) 
     .Where(whereCs); 
} 

... o ....

...  
{ 
    return list 
     .Where(whereLs) 
     .SelectMany(l => selectBs(l).Where(whereBs)) 
     .SelectMany(b => selectCs(b).Where(whereCs)); 
} 

Y las combinaciones/opciones son muchas, dependiendo de lo que tenga, cómo le gusta tener su código "(resuma un poco más o" capture "," parametrice ", p. PerCityAuthorPages(_city, _author, _numPages); etc.)

...básicamente, no me gusta tener todo el 'Dónde', 'Seleccionar' -s, etc. y para mí no es tan legible (tampoco). Mientras que con la 'forma corta' está bastante claro cuál es qué, dónde, seleccione etc. y es mucho 'short-hand' y en mucho menos caracteres.

Además, puede deffer la decisión acerca de dónde/Seleccionar combinaciones para después (hacer una o la otra basada en las necesidades, el proveedor)

Y @Telastyn es del todo bien, los proveedores de LINQ, por ejemplo, si nos fijamos en algún código de implementación,
con todas las expresiones reduciendo, etc.
son bastante no deterministas (es decir, de proveedor a proveedor) de una manera que podrían terminar mapeando, p. SQL
aunque esto debería corresponder lo mismo en la mayoría, creo.

Cuestiones relacionadas