Как создать перечислитель, который возвращает (теоретически) бесконечное количество элементов?

StackOverflow https://stackoverflow.com/questions/197297

  •  10-07-2019
  •  | 
  •  

Вопрос

Я пишу код, который выглядит примерно так:

public IEnumerable<T> Unfold<T>(this T seed)
{
    while (true)
    {
        yield return [next (T)object in custom sequence];
    }
}

Очевидно, что этот метод никогда не вернется.(Компилятор C# молча разрешает это, тогда как R# выдает предупреждение «Функция никогда не возвращается».)

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

Есть ли какие-то особые соображения для этого сценария?Мем?Перф?Другие ошибки?

Если мы всегда предоставляем условие выхода, какие есть варианты?Например:

  • объект типа T, который представляет включающую или исключительную границу
  • а Predicate<T> continue (как TakeWhile делает)
  • счет (как Take делает)
  • ...

Должны ли мы полагаться на пользователей, звонящих Take(...) / TakeWhile(...) после Unfold(...)?(Возможно, предпочтительный вариант, поскольку он использует существующие знания Linq.)

Вы бы ответили на этот вопрос по-другому, если бы код собирался публиковать в общедоступном API, либо как есть (обобщенный), либо как конкретную реализацию этого шаблона?

Это было полезно?

Решение

Если вы очень четко документируете, что метод никогда не завершит итерацию (сам метод, конечно, возвращается очень быстро), тогда я думаю, что все в порядке.Действительно, это может сделать некоторые алгоритмы намного аккуратнее.Я не верю, что есть какие-либо существенные последствия для памяти/производительности - хотя, если вы ссылаетесь на «дорогой» объект внутри своего итератора, эта ссылка будет зафиксирована.

Всегда есть способы злоупотребления API:если ваши документы ясны, я думаю, все в порядке.

Другие советы

«Вообще говоря, плохо ли оставлять перечисление, которое возвращает бесконечное количество предметов, не предоставляя способ прекратить перечисление?»

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

Должны ли мы полагаться на то, что пользователи вызывают Take (...) / Takewhile (...) после развертывания (...)?(Может быть, предпочтительный вариант, поскольку он использует существующие знания LINQ.)

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

Возвращать бесконечные последовательности — неплохая идея, функциональные языки программирования делают это уже давно.

Я согласен с Джоном.Компилятор преобразует ваш метод в класс, реализующий простой конечный автомат, который сохраняет ссылку на текущее значение (т.е.значение, которое будет возвращено через свойство Current).Я использовал этот подход несколько раз для упрощения кода.Если вы четко документируете поведение метода, все должно работать нормально.

Я бы не стал использовать бесконечный перечислитель в общедоступном API.Программисты C#, включая меня, слишком привыкли к циклу foreach.Это также будет соответствовать .NET Framework;обратите внимание, как методы Enumerable.Range и Enumerable.Repeat принимают аргумент, ограничивающий количество элементов в Enumerable.Microsoft решила использовать Enumerable.Repeat(" ", 10) вместо Enumerable.Repeat(" ").Take(10), чтобы избежать бесконечного перечисления, и я бы придерживался их выбора дизайна.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top