C#: The Dangers of Foreach
July 8, 2008
Although very handy, C#'s foreach
statement is actually quite dangerous.
In fact, I may swear off its use entirely. Why? Two reasons: (1) performance, and
(2) predictability.
Performance
Iterating through a collection using foreach
is slower than with
for
. I can't remember where I first learned that, perhaps in
Patterns & Practices:
Improving .Net Application Performance. Maybe it was from personal experience.
How much slower? Well, I suppose that depends on your particular circumstances.
Here are a few interesting references:
- FOREACH Vs. FOR
(C#): shows the extra IL code created for a
foreach
loop - To foreach or not to foreach that is the question: claims it isn't so clear-cut, at least for regular arrays. Not absolutely conclusive.
Predictability
I was looking at the C# Reference entry for foreach today and noticed this for the first time (italics added by me):
The foreach statement is used to iterate through the collection to get
the desired information, but should not be used to change the contents of the
collection to avoid unpredictable side effects
.
What's that all about? Let's take this as an example:
foreach(MyClass myObj in List<MyClass>)
Looking deeper into the
C# Language
Specification... the iteration variable is supposed to be read-only, though
apparently that doesn't stop you from updating a property of an object. Thus for
instance it would be illegal to assign a new value to myObj
, but not
to assign a new value to myObj.MyProperty
.
And that's all I can find. Why are there unpredictable side effects? I don't know. But seems best to heed Microsoft's warning.
Conclusion
Some argue that you shouldn't code for performance from the beginning, and therefore
go ahead and use foreach
whenever you want so long as you don't update
the values. In my experience that's hogwash — most of the code I work on goes into
environments where performance is extremely important. Besides, writing a for statement
requires very little extra coding compared to a foreach
statement.
Furthermore, if you have a lot going on inside your iteration block, it can be easy
to forget and accidentally update the iteration variable inside a foreach
loop. Thus do I conclude: just avoid foreach
altogether.
You should read "C# 2008 and 2005 Threaded Programming: Beginner’s Guide", by Gaston C. Hillar - http://www.packtpub.com/beginners-guide-for-C-sharp-2008-and-2005-threaded-programming/book
Amazon: http://www.amazon.com/2008-2005-Threaded-Programming-Beginners/dp/1847197108
The book is for beginners who want to exploit multi-core with C# 2005; 2008 and future 2010. It includes many topics related to avoiding side-effects using object-oriented and funcional programming with C#.
In my experience the performance difference between for and foreach is entirely insignificant unless you're doing *almost nothing* in the loop.
The readability improvement of using foreach *is* significant, however.
This sort of micro-optimization is *very* easy to implement later on after profiling tells you that it's actually worthwhile - so my conclusion is definitely to use foreach wherever it's convenient, unless I have *evidence* that it's impacting performance significantly in that particular situation.
As for the "unpredictability" side of things - I think you've misunderstood the point of the warning. The idea is that you shouldn't add or remove items from the collection over which you're iterating. Well-designed collections throw an exception if you try to (on the next step of the iteration) but it's easy to avoid in the first place. Changing the data within the object that the iteration variable refers to does no harm at all.
Furthermore, I think you'll find LINQ is pretty hard to use without foreach :)
Jon
At this point I think you're definitely right in labeling this "micro-optimization". Should only worry about it when you know you have a performance issue and really need to squeeze out every last drop. The comment about changing data was really directed at junior developers with whom I work; I've seen them create this bug for themselves. I've definitely backed away from my strong statement about avoiding foreach. But I probably won't be using it for Linq anytime soon, since I prefer using stored procedures.