Как справиться с «бесконечным» IEnumerable? - PullRequest
26 голосов
/ 29 апреля 2010

Тривиальным примером «бесконечного» IEnumerable будет

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

Я знаю, что

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

и

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

оба работают нормально (и выводят числа 0-9).

Но есть ли подводные камни при копировании или обработке выражений типа q? Можно ли рассчитывать на то, что их всегда оценивают «ленивыми»? Есть ли опасность создать бесконечный цикл?

Ответы [ 5 ]

19 голосов
/ 29 апреля 2010

Пока вы вызываете только ленивые небуферизованные методы, все будет в порядке. Так что Skip, Take, Select и т. Д. В порядке. Однако, Min, Count, OrderBy и т. Д. Сойдут с ума.

Это может сработать, но вы должны быть осторожны. Или введите Take(somethingFinite) в качестве меры безопасности (или какой-либо другой метод расширения, который выдает исключение после слишком большого количества данных).

Например:

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;
    }
}
8 голосов
/ 29 апреля 2010

Да, вам гарантировано, что приведенный выше код будет выполняться лениво. Хотя это выглядит (в вашем коде) так, как будто вы зациклились бы навсегда, ваш код на самом деле производит что-то вроде этого:

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; }
    }
}

(Это, очевидно, не точно , что будет сгенерировано, поскольку это довольно специфично для вашего кода, но, тем не менее, похоже и должно показать вам, почему его будут лениво оценивать).

5 голосов
/ 29 апреля 2010

Вам следует избегать любых жадных функций, которые пытаются прочитать до конца. Это может включать Enumerable расширения, такие как: Count, ToArray / ToList и агрегаты Avg / Min / Max и т. Д.

В бесконечных ленивых списках нет ничего плохого, но вы должны принимать осознанные решения о том, как с ними обращаться.

Используйте Take, чтобы ограничить влияние бесконечного цикла, установив верхнюю границу, даже если они вам все не нужны.

2 голосов
/ 29 апреля 2010

Да, ваш код всегда будет работать без бесконечных циклов. Кто-то может прийти позже и все испортить. Предположим, что они хотят сделать:

var q = Numbers().ToList();

Значит, ты накрылся! Многие «агрегатные» функции убьют вас, например Max().

0 голосов
/ 29 апреля 2010

Если это не ленивая оценка, ваш первый пример не будет работать так, как ожидалось.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...