Как вы разрабатываете перечислитель, который возвращает (теоретически) бесконечное количество элементов? - PullRequest
6 голосов
/ 13 октября 2008

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

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

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

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

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

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

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

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

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

Ответы [ 4 ]

7 голосов
/ 13 октября 2008

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

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

6 голосов
/ 13 октября 2008

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

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

Стоит ли полагаться на звонки пользователей? Take (...) / TakeWhile (...) после Открываются (...)? (Может быть, предпочтительнее вариант, поскольку он использует существующие Знание Linq.)

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

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

2 голосов
/ 13 октября 2008

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

0 голосов
/ 13 октября 2008

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

...