Cómo manejar un "infinito" IEnumerable?


Un ejemplo trivial de un "infinito" IEnumerable sería

IEnumerable<int> Numbers() {
  int i=0;
  while(true) {
    yield return unchecked(i++);
  }
}

Sé que

foreach(int i in Numbers().Take(10)) {
  Console.WriteLine(i);
}

Y

var q = Numbers();
foreach(int i in q.Take(10)) {
  Console.WriteLine(i);
}

Ambos funcionan bien (e imprimen el número 0-9).

¿Pero hay algún escollo al copiar o manejar expresiones como q? ¿Puedo confiar en el hecho de que siempre son evaluados "perezosos"? ¿Hay algún peligro de producir un bucle infinito?

Author: Danvil, 2010-04-29

5 answers

Sí, se le garantiza que el código anterior se ejecutará perezosamente. Si bien parece (en su código) que lo haría para siempre, su código en realidad produce algo como esto:

IEnumerable<int> Numbers()
{
    return new PrivateNumbersEnumerable();
}

private class PrivateNumbersEnumerable : IEnumerable<int>
{
    public IEnumerator<int> GetEnumerator() 
    { 
        return new PrivateNumbersEnumerator(); 
    }
}

private class PrivateNumbersEnumerator : IEnumerator<int>
{
    private int i;

    public bool MoveNext() { i++; return true; }   

    public int Current
    {
        get { return i; }
    }
}

(Esto obviamente no es exactamente lo que se generará, ya que esto es bastante específico para su código, pero no obstante es similar y debería mostrarle por qué va a ser evaluado perezosamente).

 8
Author: Adam Robinson,
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
2010-04-29 19:07:23

Mientras solo llame a métodos perezosos, sin búfer, debería estar bien. Así que Skip, Take, Select, etc están bien. Sin embargo, Min, Count, OrderBy etc se volvería loco.

Puede funcionar, pero hay que tener cuidado. O inyectar un Take(somethingFinite) como medida de seguridad (o algún otro método de extensión personalizado que arroja una excepción después de demasiados datos).

Por ejemplo:

public static IEnumerable<T> SanityCheck<T>(this IEnumerable<T> data, int max) {
    int i = 0;
    foreach(T item in data) {
        if(++i >= max) throw new InvalidOperationException();
        yield return item;
    }
}
 18
Author: Marc Gravell,
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
2010-04-29 19:03:21

Tendría que evitar cualquier función codiciosa que intente leer para terminar. Esto incluiría Enumerable extensiones como: Count, ToArray/ToList, y agregados Avg/Min/Max, etc.

No hay nada malo con las listas perezosas infinitas, pero debes tomar decisiones conscientes sobre cómo manejarlas.

Use Take para limitar el impacto de un bucle sin fin estableciendo un límite superior incluso si no los necesita todos.

 4
Author: spoulson,
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
2010-04-29 19:03:22

Sí, su código siempre funcionará sin bucle infinito. Alguien podría venir más tarde y estropear las cosas. Supongamos que quieren hacer:

var q = Numbers().ToList();

Entonces, ¡estás jodido! Muchas funciones "agregadas" te matarán, como Max().

 2
Author: Patrick Karcher,
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
2010-04-29 19:04:44

Si no fue una evaluación perezosa, su primer ejemplo no funcionará como se esperaba en primer lugar.

 0
Author: Ron Klein,
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
2010-04-29 19:04:47