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

26
задан Danvil 29 April 2010 в 19:15
поделиться

5 ответов

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

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

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

8
ответ дан 28 November 2019 в 07:40
поделиться

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

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

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

5
ответ дан 28 November 2019 в 07:40
поделиться

Пока вы вызываете только ленивые, небуферизованные методы, все будет в порядке. Так что 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;
    }
}
19
ответ дан 28 November 2019 в 07:40
поделиться

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

0
ответ дан 28 November 2019 в 07:40
поделиться

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

var q = Numbers().ToList();

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

2
ответ дан 28 November 2019 в 07:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: