2010-06-07 11 views

Respuesta

24

Cuando quiere deferred execution.

Esto tiene sentido en la mayoría de los casos donde la alternativa es construir una colección temporal.

Considere este escenario: Tengo una lista de enteros y quiero enumerar sus cuadrados.

que podía hacer esto:

public static IEnumerable<int> Squares(this IEnumerable<int> numbers) { 
    List<int> squares = new List<int>(); 
    foreach (int number in numbers) 
     squares.Add(number * number); 

    return squares; 
} 

Entonces podría resumir las plazas, ocupar su promedio, encontrar el más grande, etc.

Pero realmente no necesita para llenar toda una nueva List<int> para ese propósito. Podría haber utilizado para enumerar yield sobre la lista inicial y devolver los cuadrados uno por uno:

public static IEnumerable<int> Squares(this IEnumerable<int> numbers) { 
    foreach (int number in numbers) 
     yield return number * number; 
} 

El hecho de que esto realmente hace la diferencia podría no ser evidente hasta que empezar a tratar con colecciones muy grandes, donde poblar colecciones temporales resulta ser un desperdicio.

Por ejemplo, supongamos que quería encontrar el primer cuadrado por encima de un cierto umbral. Yo podría hacer esto:

IEnumerable<int> numbers = GetLotsOfNumbers(); 
var squares = numbers.Squares(); 
int firstBigSquare = squares 
    .Where(x => x >= 1000) 
    .FirstOrDefault(); 

Pero si mi método Squares poblada toda una List<int> antes de regresar, el código anterior sería hacer potencialmente mucho más trabajo de lo necesario.

+2

Bien escrito ejemplo. + 1 por qué la diferencia puede no ser evidente. – Paddy

10

Desde el MSDN page on yield:

usados ​​en un bloque de iterador para proporcionar un valor para el objeto empadronador o para señalar el final de iteración.

Lo utiliza al crear un iterador personalizado. Usando el ejemplo de la página:

// yield-example.cs 
using System; 
using System.Collections; 
public class List 
{ 
    public static IEnumerable Power(int number, int exponent) 
    { 
     int counter = 0; 
     int result = 1; 
     while (counter++ < exponent) 
     { 
      result = result * number; 
      yield return result; 
     } 
    } 

    static void Main() 
    { 
     // Display powers of 2 up to the exponent 8: 
     foreach (int i in Power(2, 8)) 
     { 
      Console.Write("{0} ", i); 
     } 
    } 
} 

El yield significa que el bucle interior whilePower efectivamente "pausa" después de cada iteración para permitir que la rutina de llamada para realizar alguna acción. En este caso, imprimiendo el resultado.

8

Cuando estás demasiado vago para escribir su propio IEnumerator;)

+3

+1 Creo que siempre soy demasiado vago ... –

+1

Y probablemente debería notarse que hay una diferencia entre IEnumerator e IEnumerable, las palabras están deletreadas de manera similar, solía confundirme porque estaba leyendo diferentes definiciones para lo que pensamiento era lo mismo – Davy8

+0

@ Davy8 muy cierto! Mucha gente está confundida por la diferencia. IEnumerator contiene toda la lógica oculta detrás del rendimiento; IEnumerable simplemente proporcionaría acceso al IEnumerator. –

1

Ver this article.

El rendimiento actúa como marcador de retorno: es un punto de retorno no local que preserva el entorno del método y permite que el código "vuelva a saltar".De manera similar (tipo de invertido) a pasar un delegado a un método que le permite inyectar lógica específica dentro de otro método, los cierres le permiten hacer diferentes tipos de trabajo "alrededor" de un método más general, lo que le permite mantener el código pequeño y modular y reutilizable.

Esto podría generar un código mucho más eficiente. En lugar de ejemplificar una colección muy grande, es posible permitir actuar sobre objetos individuales en secuencia (y se descartan después de cada operación). Me imagino que podrías construir casos en los que un iterador directo sería extremadamente difícil de construir.

Cuestiones relacionadas