¿Por qué usar la palabra clave yield, cuando podría usar unerableumerable ordinario?


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 palabra clave yield. Le dice al compilador que construya un cierto tipo de cosas (un iterador). Pero ¿por qué usarlo? Aparte de ser un poco menos de código, ¿qué hace por mí?

 165
Author: Marcel Gosselin, 2012-12-27

8 answers

Usar yieldhace que la colección sea perezosa.

Digamos que solo necesita los primeros cinco elementos. A tu manera, tengo que recorrer toda la lista para obtener los primeros cinco elementos. Con yield, solo hago un bucle a través de los primeros cinco elementos.

 232
Author: Robert Harvey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-27 16:28:09

El beneficio de los bloques iteradores es que funcionan perezosamente. Así que 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 secuencia el tiempo que desee, nunca almacenando en búfer más de un solo 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 un flujo infinito usando 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ía una secuencia infinita en una lista?

Mi Edulinq blog series da una implementación de ejemplo de LINQ a Objetos que hace pesado uso de bloques iteradores. LINQ es fundamentalmente perezoso donde puede ser , y poner las cosas en una lista simplemente no funciona de esa manera.

 126
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-05 19:33:59

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

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

 42
Author: Hans Kesting,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-27 16:29:19

Puede usar yield para devolver elementos que no están en una lista. Aquí hay una pequeña muestra que podría iterar infinitamente a través de 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

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

 22
Author: Jason Whitted,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-03 06:50:54
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 asumiendo item.Name = = "James" será satisfecho en el segundo ítem de la lista, el método usando yield dará 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 que llama.

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

 10
Author: humblelistener,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-03 09:16:34

El mejor ejemplo del mundo real que he visto para el uso de yield serí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 volverá:

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 rápida y fácilmente, dándole la capacidad de usar las extensiones Linq y consultar solo lo que necesita.

 7
Author: Middas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-22 22:42:17

¿Por qué usar [yield]? Aparte de ser un poco menos de código, ¿qué hace por mí?

A veces es útil, a veces no. Si todo el conjunto de datos debe ser examinado y devuelto, entonces no va a haber ningún beneficio en el uso de 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 ordenar. Supongamos que tiene una lista de objetos que contienen una fecha y una cantidad en dólares de este año y le gustaría ver el primer puñado (5) registros del año.

Para lograr esto, la lista debe ordenarse ascendentemente por fecha, y luego tener los primeros 5 tomados. Si esto se hacía sin rendimiento, la lista completa tendría que ordenarse, hasta asegurarse de que las dos últimas fechas estuvieran en orden.

Sin embargo, con yield, 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.

 1
Author: Travis J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-23 23:31:38

La declaración yield return le permite devolver solo un artículo a la vez. Está recogiendo todos los elementos de una lista y devolviendo de nuevo esa lista, que es una sobrecarga de memoria.

 0
Author: Prabhavith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-03 06:49:15