Подтвердить что ты не робот

Как обрабатывать "бесконечный" IEnumerable?

Тривиальный пример "бесконечного" 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? Могу ли я полагаться на то, что они всегда оцениваются "ленивыми"? Есть ли опасность создать бесконечный цикл?

4b9b3361

Ответ 1

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

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

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

Ответ 2

Пока вы называете только ленивые, небуферизованные методы, все должно быть хорошо. Так что 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;
    }
}

Ответ 3

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

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

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

Ответ 4

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

var q = Numbers().ToList();

Тогда ты хочешь! Многие "агрегатные" функции убьют вас, например Max().

Ответ 5

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