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?
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).
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;
}
}
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.
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()
.
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.
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