How do you design an enumerator that returns (theoretically) an infinite amount of items?

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

  •  10-07-2019
  •  | 
  •  

Question

I'm writing code that looks similar to this:

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

Obviously, this method is never going to return. (The C# compiler silently allows this, while R# gives me the warning "Function never returns".)

Generally speaking, is it bad design to provide an enumerator that returns an infinite number of items, without supplying a way to stop enumerating?

Are there any special considerations for this scenario? Mem? Perf? Other gotchas?

If we always supply an exit condition, which are the options? E.g:

  • an object of type T that represents the inclusive or exclusive boundary
  • a Predicate<T> continue (as TakeWhile does)
  • a count (as Take does)
  • ...

Should we rely on users calling Take(...) / TakeWhile(...) after Unfold(...)? (Maybe the preferred option, since it leverages existing Linq knowledge.)

Would you answer this question differently if the code was going to be published in a public API, either as-is (generic) or as a specific implementation of this pattern?

Was it helpful?

Solution

So long as you document very clearly that the method will never finish iterating (the method itself returns very quickly, of course) then I think it's fine. Indeed, it can make some algorithms much neater. I don't believe there are any significant memory/perf implications - although if you refer to an "expensive" object within your iterator, that reference will be captured.

There are always ways of abusing APIs: so long as your docs are clear, I think it's fine.

OTHER TIPS

"Generally speaking, is it bad desing to provide an enumerator that returns an infinite amount of items, without supplying a way to stop enumerating?"

The consumer of the code, can always stop enumerating (using break for example or other means). If your enumerator returns and infinite sequence, that doesn't mean the client of the enumerator is somehow forced to never break enumeration, actually you can't make an enumerator which is guaranteed to be fully enumerated by a client.

Should we rely on users calling Take(...) / TakeWhile(...) after Unfold(...)? (Maybe the preferred option, since it leverages existing Linq knowledge.)

Yes, as long as you clearly specify in your documentation that the enumerator returns and infinite sequence and breaking of enumeration is the caller's responsibility, everything should be fine.

Returning infinite sequences isn't a bad idea, functional programing languages have done it for a long time now.

I agree with Jon. Compiler transforms your method to class implementing simple state machine that keeps reference to current value (i.e. value that will be returned via Current property). I used this approach several times to simplify code. If you clearly document method's behavior it should work just fine.

I would not use an infinite enumerator in a public API. C# programmers, myself included, are too used to the foreach loop. This would also be consistent with the .NET Framework; notice how the Enumerable.Range and Enumerable.Repeat methods take an argument to limit the number of items in the Enumerable. Microsoft chose to use Enumerable.Repeat(" ", 10) instead of Enumerable.Repeat(" ").Take(10) to avoid the infinite enumeration and I would adhere to their design choices.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top