Iterator methods in C# or one of my favorite features of that language. I do not use it all that often, but it is nice to know it is there. If you are not sure what they are, here’s a little example:
public IEnumerable<int> Iota(int from, int count)
{
for (int offset = 0; offset < count; ++offset)
yield return from + offset;
}
They allow you to lazily generate any sequence directly in code. For example, I like to use them when generating a list of errors on a complex input (think compiler errors). The presence of the yield
contextual keyword transforms the function body into a state machine, allowing you to pause it until you need the next value.
However, this makes it a little more difficult to compose such iterator methods, and in reverse, refactor a complex iterator method into several smaller ones. It was not obvious to me right away how to do it at all in a ‘this always works’ manner, so I am sharing how I do it here. Consider this slightly more complex iterator method:
public IEnumerable<int> IotaAndBack(int from, int count)
{
for (int offset = 0; offset < count; ++offset)
yield return from + offset;
for (int offset = 0; offset < count; ++offset)
yield return from + count - offset - 1;
}
Now we want to extract both loops into their own functions. My one-size-fits-all solution is this:
public IEnumerable<int> AndBack(int from, int count)
{
for (int offset = 0; offset < count; ++offset)
yield return from + count - offset - 1;
}
public IEnumerable<int> IotaAndBack(int from, int count)
{
foreach (var x in Iota(from, count))
yield return x;
foreach (var x in AndBack(from, count))
yield return x;
}
As you can see, a little ‘foreach harness’ is needed to compose the parts into the outer function. Of course, in a simple case like this, the LINQ version Iota(from, count).Concat(AndBack(from, count))
also works. But that only works when the outer function is sufficiently simple.