What is infinity? It's commonly represented as the lemniscate (∞), that funny sideways 8 that conjures to mind a Mobius strip. If you wanted to get hard core about it you could start talking about Cantor sets, aleph zero, aleph one and suchlike. You also get all sorts of cool paradoxes with inifinities, such as Hilberts Grand Hotel. Anyway, to get back on track, that funky little symbol allows us to imagine a major misconception, that infinity is a thing, it's not, it's a process, an algorithm if you like. For example, the infinity of natural numbers can be represented as such:
int i = 1;
while (true)
{
yield return i++;
}
Of course we all know that would lead quickly to an overflow exception. Nonetheless the algorithm expresses an infinity. No matter what happens, another trip round the loop conjures up a bigger number, and it will never, ever end. What is especially interesting about putting it in an iterator means that we only get the next item when we need it. Thus it is possible to create "inifinite" algorithms for many classes of number where we do not have to know the whole sequence up front but can generate it on the go as it were. Primes and Fibonacci numbers are just two examples. Imagine we create an iterator that in theory would enumerate all primes it is possible to express in a 64-bit number. That's a lot of primes, however we may not need all of them. For example if we were attempting to crack a message encypted with a private key which is the product of two primes, we could set two copies of our iterator going, each generating primes as it goes, and stop when we find the match. Whilst our iterator in theory never ends, in practise it does end when we find what we're looking for. Obviously we could be even cleverer and feed a single prime generating iterator as an input into a Cartesian Product iterator.
Here's a slightly different use of an "infinite" iterator: as a task scheduler. Imagine that we have a set of recurring tasks, so we have a thread that will service those tasks. How it operates is it does a foreach on an iterator and executes the task (which could be a class, delegate etc) as it receives them. Here's some pseudo code for the iterator:
while (true)
{
if (exit)
{
yield break;
}
yield return CurrentTask();
Sleep();
}
Interesting isn't it? The calling thread does not need to care about times or sleeping or anything like that, it just happily enumerates the iterator until it the iterator ends. The iterator sleeps when it needs to sleep, ends when it needs to end, and delivers tasks when they need to be executed.
There's other occasions where you might be enumerating items with no real end. One would be a kind of discovery engine. Imagine that you send out a UDP broadcast on a given port, and then listen for incoming responses, as well as other machines broadcasting. You never really end since a new machine could come online at any time. No problem, hook up all your listening code inside an iterator, and then foreach your way through machines either until you've got enough, have to shut down or whatever.
By hiding such complex threaded code inside what appears at first glance to be a simple collection type class you save the developers using your class an awful lot of trouble. So they're not just for collection classes, well I suppose you could argue that they still are, but now the collection potential size can be "grown" to represent inifinite collections.
Try doing that with an array!