Introduction
Just moved to C#? Do you miss the for i = 1 to 50
syntax? Look no further, C# can do it too by extending it in a unique way.
Why more for?
The first question is why? Typically this is only asked by C++ and C# developers. C# developers love their for
loop, they say it's elegant, flexible, robust, and that nothing else is needed. I agree on some of their points. It is flexible. It is robust. Technically nothing else is needed, but something else is warranted. As for elegance, well whenever a developer comes to me and says something is elegant, I cringe...
The C style for
loop is as follows:
for ([initializers]; [expression]; [iterators])
To count from 1 to 5:
for (int i = 1; i <= 5; i++)
Console.WriteLine(i);
}
Now this is all fine and dandy, and it's certainly extensible. The expression and even the iterators can use all kinds of complex evaluators. But in 99% of the cases, a simple int
x to y solution is implemented. C# has a foreach
, so the usage of the for
loop is greatly reduced. In cases where for
is still used, it is still primarily used to perform this simple counting function.
For such a simple counting function, the for
loop is a bit of overkill syntactically. The problem is that this syntax is a roach point. What is a roach? A roach is a bug that recurs often. A roach point, is a place where roaches breed. Developers moving into C# from other languages very often make mistakes in writing this function. Instead of i <= 5, they write i = 5, or i < 5. A simple mistake, but a very common one. Another common mistake is this - let's use a 0 based loop. 0 to 4 - quick - what changes need to be made to count from 0 to 4? Did you have to think about this *at all*? If so - poof there's room for a roach.
Now C++ developers are quick to jump on me that only a fool would be so stupid as to not understand the syntax. But it's not about understanding this, it's about making mistakes with it. And I consistently see such stupid mistakes made with the for
loop that lead to bugs. And while it's certainly higher with those who are new to C#, I have seen the most skilled C++ developers make this novice mistake as well. Roach Point! Now I am not advocating eliminating the beloved C style for
- simply offering an alternative so that the C style for
is only used when it's needed, which is about 1% of the time.
How for?
Both C# and .NET are wonderfully extensible - something I am eternally grateful for. Using this extensibility, C# can be easily made to support the following syntax:
foreach (int i in new Counter(1, 5)) {
Console.WriteLine(i.ToString());
}
Neat huh? And guess what - your team won't make any of those stupid mistakes with this format. It can even accept an optional third parameter, step that allows incrementing of, other than 1. I have not implemented reverse counting, but that's easily done as well.
Alternate syntax
Some users object to the use of the new
keyword. There is nothing technically wrong with it, just some users find it annoying. By using static
methods, the syntax can be changed to:
foreach (int i in Counter.Loop(1, 5)) {
Console.WriteLine(i.ToString());
}
Either syntax is perfectly acceptable. For my personal use, I've chosen to stay with the new
operator. In fact, the class could easily be expanded to support both.
Other Languages
Let's compare this now with the for
loop in other languages:
Visual Basic:
for i = 1 to 5
Delphi:
for i := 1 to 5 do
Python:
foreach(i in range(1, 5))
At the time I wrote this, I was not aware of the Python syntax. But it bears a striking resemblance.
Drawbacks
It's a tiny tad bit slower. Unless you have a very tight loop with very little code inside of it, there is no measurable difference. If it eliminates even one bug that costs a customer time or money, then I'd say that's worth a picosecond borrowed from each and every one of your customers. I'd certainly be willing to give up a fair number of picoseconds if the software I use on a daily basis was a bit more reliable. Mind you I'm not talking about making applications run slower - but small changes like this will never be noticed in the scheme of things time wise.
It also requires a few more bytes of memory. But I've seen too many programmers come to me and say "look I saved 20 bytes!" and ignoring the fact that their code is more complex, that the program is already 20 Mb, and that they spent 4 hours doing it. Unless we are talking about a few bytes in a large array, yes I would definitely agree these few bytes are worth it. Let's assume its memory footprint is 24 bytes. How many loops are active in a nested fashion at once? Let's imagine your code is deep down in a series of method calls for a total of 10 loops. 10 x 24 = 240. Do you think I care that my .NET app is using 240 more bytes? I doubt you would notice 240 bytes added to a typical Hello World in .NET.
The loss of efficiency pales in comparison. We need not be wasteful of faster CPUs, but as an industry as a whole we should be focusing on more reliable software. Which is what .NET is all about anyways.... Initializing of memory, memory checking, bounds checking, all the things we should have been doing for years.
Code
The code is very simple. The for
loop is implemented by making an iterator that returns an integer. If you want another example of extending C# in an interesting way, you can check out Easier database transactions - Extending the using statement to perform automatic database transactions.
public class Counter
{
public class CounterEnumerator
{
private Counter _Counter;
internal CounterEnumerator(Counter aCounter)
{
_Counter = aCounter;
}
public bool MoveNext()
{
_Counter._Current += _Counter._Step;
return _Counter._Current <= _Counter._End;
}
public int Current
{
get { return _Counter._Current; }
}
}
private int _Begin;
private int _End;
private int _Step;
private int _Current;
public Counter(int aBegin, int aEnd) : this(aBegin, aEnd, 1)
{
}
public Counter(int aBegin, int aEnd, int aStep)
{
_Current = aBegin - 1;
_Begin = aBegin;
_End = aEnd;
_Step = aStep;
}
public CounterEnumerator GetEnumerator()
{
return new CounterEnumerator(this);
}
}
One more Crumb of Food for Thought
for (UInt32 i = mPageCount - 1; i >= 0; i--) {
}
This is a piece of code I came across recently. Did you notice anything wrong with it? Very few readers will. This was attested to by first posting this in a developers forum where only a very few people even noticed that there was a problem, even after I hinted that there was an issue to be found. The above code will not function as the author expected and was the source of a bug that I was tasked with fixing. The correct code is:
for (UInt32 i = mPageCount - 1; i != UInt32.MaxValue; i--) {
}