2012-04-05 4 views
6

En LINQ Where es un operador de transmisión. Where-as OrderByDescending no es un operador de streaming. AFAIK, un operador de streaming solo reúne el siguiente elemento que es necesario. Un operador que no realiza streaming evalúa todo el flujo de datos a la vez.¿En qué se diferencia un operador de transmisión de la ejecución diferida?

No veo la importancia de definir un operador de transmisión. Para mí, es redundante con ejecución diferida. Tome el ejemplo donde escribí una extensión personalizada y la consumí usando el operador where y orderby.

public static class ExtensionStuff 
{ 
    public static IEnumerable<int> Where(this IEnumerable<int> sequence, Func<int, bool> predicate) 
    { 
     foreach (int i in sequence) 
     { 
      if (predicate(i)) 
      { 
       yield return i; 
      } 
     } 
    } 
} 

    public static void Main() 
    { 
     TestLinq3(); 
    } 

    private static void TestLinq3() 
    { 
     int[] items = { 1, 2, 3,4 }; 

     var selected = items.Where(i => i < 3) 
          .OrderByDescending(i => i); 

     Write(selected); 
    } 



    private static void Write(IEnumerable<int> selected) 
    { 
     foreach(var i in selected) 
      Console.WriteLine(i); 
    } 

En cualquier caso Where, tiene que evaluar cada elemento con el fin de determinar qué elementos cumple la condición. El hecho de que rinda parece ser relevante solo porque el operador obtiene una ejecución diferida.

Entonces, ¿cuál es la importancia de los operadores de transmisión por secuencias?

+5

Inténtalo de nuevo con aproximadamente 2 mil millones de entradas en 'artículos'. – cHao

+2

@cHao o una secuencia infinita, o una secuencia derivada de una secuencia de red abierta. –

+0

[Ejemplo más específico] (http://codereview.stackexchange.com/a/9777/8246) –

Respuesta

11

Hay dos aspectos: velocidad y memoria.

El aspecto de velocidad se vuelve más aparente cuando se usa un método como .Take() para consumir solo una parte del conjunto de resultados original.

// Consumes ten elements, yields 5 results. 
Enumerable.Range(1, 1000000).Where(i => i % 2 == 0) 
    .Take(5) 
    .ToList(); 

// Consumes one million elements, yields 5 results. 
Enumerable.Range(1, 1000000).Where(i => i % 2 == 0) 
    .OrderByDescending(i => i) 
    .Take(5) 
    .ToList(); 

Debido a que el primer ejemplo utiliza sólo los operadores de streaming antes de la llamada a Take, sólo terminan produciendo valores de 1 a 10 antes Take paradas de evaluación. Además, solo se carga un valor en la memoria a la vez, por lo que tiene una huella de memoria muy pequeña.

En el segundo ejemplo, OrderByDescending no se está transmitiendo, por lo que en el momento Take tira del primer elemento, el resultado completo que pasó a través del filtro Where debe colocarse en la memoria para su clasificación. Esto podría llevar mucho tiempo y producir una gran huella de memoria.

Incluso si no estaba usando Take, el problema de memoria puede ser importante. Por ejemplo:

// Puts half a million elements in memory, sorts, then outputs them. 
var numbers = Enumerable.Range(1, 1000000).Where(i => i % 2 == 0) 
    .OrderByDescending(i => i); 
foreach(var number in numbers) Console.WriteLine(number); 

// Puts one element in memory at a time. 
var numbers = Enumerable.Range(1, 1000000).Where(i => i % 2 == 0); 
foreach(var number in numbers) Console.WriteLine(number); 
2

El hecho de que se produce sólo parece ser relevante debido a que las ganancias del operador diferidas ejecución.

Entonces, ¿cuál es la importancia de los operadores de transmisión por secuencias?

I.e. no se pueden procesar secuencias infinitas con métodos de extensión de búfer/no transmisión, mientras que se puede "ejecutar" una secuencia de ese tipo (hasta que se interrumpa) simplemente usando solo métodos de extensión de transmisión.

Tomemos por ejemplo este método:

public IEnumerable<int> GetNumbers(int start) 
{ 
    int num = start; 

    while(true) 
    { 
     yield return num; 
     num++; 
    } 
} 

Usted puede utilizar Where bien:

foreach (var num in GetNumbers(0).Where(x => x % 2 == 0)) 
{ 
    Console.WriteLine(num); 
} 

OrderBy() no funcionaría en este caso, ya que tendría que enumerar de forma exhaustiva los resultados antes de emitir una un solo numero.

+1

Nitpick, pero es infinito, es solo que cada resultado no es único. Eventualmente se desbordará, pero se envuelve, por lo que puede desbordarse un número infinito de veces. (Siempre y cuando no estés en un bloque marcado). Podrías tener 'while (true) yield return 4;' si quisieras. – Servy

+0

Aah, eso es correcto ;-) – BrokenGlass

2

Solo para ser explícito; en el caso que usted mencionó, no hay ventaja en el hecho de que en las transmisiones, ya que el orden lo absorbe todo de todos modos.Sin embargo, hay veces en que se utilizan las ventajas de la transmisión (otras respuestas/comentarios han dado ejemplos), por lo que todos los operadores de LINQ transmiten lo mejor que puedan. Orderby arroyos tanto como puede, que no es mucho. Donde fluye de manera muy efectiva.

+0

¿Hay operadores LINQ cuya transmisión se encuentre entre "Where" y "OrderBy"? –

+0

@ZevSpitz Supongo que dependerá de cómo defina sus términos. 'SkipWhile' podría calificar, según cómo defina la cantidad de" transmisión ". – Servy

+0

[MSDN] (https://msdn.microsoft.com/en-us/library/mt693095.aspx) define la transmisión como capaz de analizar los resultados de a uno por vez (por ejemplo, "Seleccionar"), mientras que la no transmisión requiere todos los valores para evaluar el primer valor (por ejemplo, 'OrderBy'). Según esa definición, ¿por qué 'SkipWhile' debería tener una transmisión menor que' Select'? La primera iteración no requiere todos los valores. –

Cuestiones relacionadas