2010-04-29 3 views
25

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

+1

Как раз быть придирчивым, разве ваш «бесконечный» пример не выдает исключение, когда i = Int32.MaxValue и вы делаете i ++?Или он переходит в Int32.MinValue? Хмм ..... –

+0

Вы правы. Вероятно, это будет исключение переполнения ... Я отредактирую его. – Danvil

+0

Просто будучи придирчивым, ваша точка все еще сталкивалась. :) Кроме того, я попробовал, и он работает в Int32.MinValue. No OverflowException, поэтому он был фактически бесконечным циклом. –

ответ

8

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

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

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

18

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

+1 У меня есть конкурирующий ответ, но мне просто нужно дать преимущество для метода расширения для IEnumerable, называемого * SanityCheck *. Лучшее имя функции когда-либо. Я собираюсь реализовать этот ... –

+0

+1 для указания этого. Также, если OP фактически вызывает 'foreach (int i в Numbers()), что также вызовет проблемы. – Felype

4

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

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

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

2

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

var q = Numbers().ToList(); 

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

0

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

 Смежные вопросы

  • Нет связанных вопросов^_^