¿Por qué usar la palabra clave de rendimiento, cuando podría usar un IEnumerable ordinario?


171

Dado este código:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}

¿Por qué no debería simplemente codificarlo de esta manera ?:

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

Entiendo lo que hace la yieldpalabra clave. Le dice al compilador que construya cierto tipo de cosas (un iterador). ¿Pero por qué usarlo? Además de ser un poco menos de código, ¿qué hace por mí?


28
Me doy cuenta de que esto es solo un ejemplo, pero realmente, el código debería escribirse así: FullList.Where(IsItemInPartialList):)
BlueRaja - Danny Pflughoeft

Respuestas:


241

El uso yieldhace que la colección sea perezosa.

Digamos que solo necesita los primeros cinco elementos. A su manera, tengo que recorrer toda la lista para obtener los primeros cinco elementos. Con yield, solo recorro los primeros cinco elementos.


14
Tenga en cuenta que el uso FullList.Where(IsItemInPartialList)será igual de perezoso. Solo que requiere mucho menos código personalizado --- gunk --- generado por el compilador. Y menos tiempo para desarrolladores escribiendo y manteniendo. (Por supuesto, eso fue sólo este ejemplo)
sehe

44
Ese es Linq, ¿no? Me imagino que Linq hace algo muy similar debajo de las sábanas.
Robert Harvey

1
Sí, Linq utilizó la ejecución retrasada ( yield return) siempre que sea posible.
Chad Schouggins

11
No olvide que si la declaración de rendimiento de rendimiento nunca se ejecuta, aún obtendrá un resultado de colección vacío, por lo que no debe preocuparse por una excepción de referencia nula. el rendimiento del rendimiento es increíble con chispitas de chocolate.
Razor

127

El beneficio de los bloques iteradores es que funcionan perezosamente. Entonces puedes escribir un método de filtrado como este:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
                                   Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

Eso le permitirá filtrar una transmisión todo el tiempo que desee, sin almacenar más de un elemento a la vez. Si solo necesita el primer valor de la secuencia devuelta, por ejemplo, ¿por qué querría copiar todo en una nueva lista?

Como otro ejemplo, puede crear fácilmente una secuencia infinita utilizando bloques iteradores. Por ejemplo, aquí hay una secuencia de números aleatorios:

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

¿Cómo almacenarías una secuencia infinita en una lista?

Mi serie de blogs Edulinq ofrece una implementación de ejemplo de LINQ to Objects que hace un uso intensivo de los bloques iteradores. LINQ es fundamentalmente vago donde puede estar, y poner las cosas en una lista simplemente no funciona de esa manera.


1
No estoy seguro de si te gusta RandomSequenceo no. Para mí, IEnumerable significa, en primer lugar, que puedo iterar con foreach, pero esto obviamente conduciría a un bucle infinito aquí. Consideraría esto un mal uso bastante peligroso del concepto IEnumerable, pero YMMV.
Sebastian Negraszus

55
@SebastianNegraszus: una secuencia de números aleatorios es lógicamente infinita. Podría crear fácilmente una IEnumerable<BigInteger>representación de la secuencia de Fibonacci, por ejemplo. Puede usarlo foreach, pero nada IEnumerable<T>garantiza que será finito.
Jon Skeet

42

Con el código de "lista", debe procesar la lista completa antes de poder pasar al siguiente paso. La versión de "rendimiento" pasa el artículo procesado inmediatamente al siguiente paso. Si ese "próximo paso" contiene un ".Take (10)", entonces la versión "rendimiento" solo procesará los primeros 10 elementos y se olvidará del resto. El código de "lista" habría procesado todo.

Esto significa que ve la mayor diferencia cuando necesita hacer mucho procesamiento y / o tiene largas listas de elementos para procesar.


23

Puede usar yieldpara devolver artículos que no están en una lista. Aquí hay una pequeña muestra que podría recorrer infinitamente una lista hasta que se cancele.

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

Esto escribe

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

... etc. a la consola hasta que se cancele.


10
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

Cuando el código anterior se usa para recorrer FilteredList () y suponiendo que item.Name == "James" se satisfará en el segundo elemento de la lista, el método que se use yieldrendirá dos veces. Este es un comportamiento perezoso.

Donde como el método que usa list agregará todos los n objetos a la lista y pasará la lista completa al método de llamada.

Este es exactamente un caso de uso donde se puede resaltar la diferencia entre IEnumerable e IList.


7

El mejor ejemplo del mundo real que he visto para el uso yieldsería calcular una secuencia de Fibonacci.

Considere el siguiente código:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

Esto devolverá:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

Esto es bueno porque le permite calcular una serie infinita de forma rápida y sencilla, lo que le permite utilizar las extensiones de Linq y consultar solo lo que necesita.


55
No puedo ver nada "mundo real" en un cálculo de secuencia de Fibonacci.
Nek

Estoy de acuerdo en que esto no es realmente "el mundo real", pero qué buena idea.
Casey

1

¿Por qué usar [rendimiento]? Además de ser un poco menos de código, ¿qué hace por mí?

A veces es útil, a veces no. Si el conjunto completo de datos debe ser examinado y devuelto, no habrá ningún beneficio en el uso del rendimiento porque todo lo que hizo fue introducir gastos generales.

Cuando el rendimiento realmente brilla es cuando solo se devuelve un conjunto parcial. Creo que el mejor ejemplo es la clasificación. Suponga que tiene una lista de objetos que contienen una fecha y un monto en dólares de este año y le gustaría ver los primeros (5) registros del año.

Para lograr esto, la lista debe ordenarse de forma ascendente por fecha y luego tomar los primeros 5. Si esto se hiciera sin rendimiento, toda la lista tendría que ser ordenada, hasta asegurarse de que las dos últimas fechas estuvieran en orden.

Sin embargo, con el rendimiento, una vez que se han establecido los primeros 5 elementos, la clasificación se detiene y los resultados están disponibles. Esto puede ahorrar una gran cantidad de tiempo.


0

La declaración de devolución de rendimiento le permite devolver solo un artículo a la vez. Está recopilando todos los elementos en una lista y está volviendo a devolver esa lista, que es una sobrecarga de memoria.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.