2010-11-12 5 views
5

Estoy tratando de entender lo que hace el compilador C# cuando estoy encadenando métodos linq, particularmente cuando encadenar el mismo método varias veces.Comprender cómo el compilador de C# trata con los métodos de encadenamiento linq

Ejemplo simple: Digamos que estoy tratando de filtrar una secuencia de entradas basada en dos condiciones.

La primera cosa que puede hacer es algo como esto:

IEnumerable<int> Method1(IEnumerable<int> input) 
{ 
    return input.Where(i => i % 3 == 0 && i % 5 == 0); 
} 

Pero podría también encadenar los métodos en los que, con una sola condición en cada uno:

IEnumerable<int> Method2(IEnumerable<int> input) 
{ 
    return input.Where(i => i % 3 == 0).Where(i => i % 5 == 0); 
} 

que tenía una mira el IL en Reflector; es obviamente diferente para los dos métodos, pero analizándolo más está más allá de mis conocimientos en el momento :)

me gustaría saber:
a) lo que hace el compilador de manera diferente en cada caso, y por qué.
b) ¿existen implicaciones de rendimiento (no tratando de optimizar el micro-;! Curiosidad)

Respuesta

9

La respuesta a (a) es corto, pero voy a entrar en más detalle a continuación:

El compilador no hace realmente el encadenamiento - sucede en tiempo de ejecución, a través de la normalidad organización de los objetos! Aquí hay mucha menos magia de lo que podría parecer a primera vista: Jon Skeet recently completed the "Where clause" step en su serie de blogs, Reimplementando LINQ to Objects. Yo recomendaría leer todo eso.

En términos muy cortos, lo que sucede es lo siguiente: cada vez que se llama al método de extensión Where, devuelve un nuevo objeto WhereEnumerable que tiene dos cosas - una referencia a la anterior IEnumerable (el que llama Where sucesivamente), y la lambda que proporcionaste

Cuando se inicia la iteración en este WhereEnumerable (por ejemplo, en un foreach más adelante en su código), internamente simplemente comienza la iteración en la que IEnumerable se ha hecho referencia.

"Este foreach me acaba de pedir para el siguiente elemento en mi secuencia, así que estoy girando alrededor y preguntando por el siguiente elemento de la secuencia de ".

Eso recorre toda la cadena hasta que llegamos al origen, que en realidad es una especie de matriz o almacenamiento de elementos reales.Como cada Enumerable dice "OK, aquí está mi elemento" pasándolo de nuevo por la cadena, también aplica su propia lógica personalizada. Para un Where, aplica el lambda para ver si el elemento cumple los criterios. Si es así, le permite continuar a la siguiente persona que llama. Si falla, se detiene en ese punto, vuelve a su enumerado Enumerable y pregunta por el siguiente elemento.

Esto sigue sucediendo hasta que MoveNext de todos devuelve falso, lo que significa que la enumeración está completa y no hay más elementos.

Para responder (b), hay siempre una diferencia, pero aquí es demasiado trivial para molestarse con. No te preocupes :)

+0

Nice answer. Necesitamos más material como este en Stackoverflow. –

1
  1. El primer utilizará un iterador, el segundo usará dos. Es decir, el primero establece una tubería con una etapa, la segunda implicará dos etapas.

  2. Dos iteradores tienen una ligera desventaja de rendimiento en uno.

Cuestiones relacionadas