When I write code in Visual Studio, ReSharper (God bless it!) often suggests me to change my old-school for loop in the more compact foreach form.
And often, when I accept this change, ReSharper goes a step forward, and suggests me to change it again, in a shiny LINQ form.
So, I wonder: are there some real advantages, in these improvements? In pretty simple code execution, I cannot see any speed boost (obviously), but I can see the code becoming less and less readable… So I wonder: is it worth it?
Best Answer
for
vs.foreach
There is a common confusion that those two constructs are very similar and that both are interchangeable like this:
and:
The fact that both keywords start by the same three letters doesn't mean that semantically, they are similar. This confusion is extremely error-prone, especially for beginners. Iterating through a collection and doing something with the elements is done with
foreach
;for
doesn't have to and shouldn't be used for this purpose, unless you really know what you're doing.Let's see what's wrong with it with an example. At the end, you'll find the full code of a demo application used to gather the results.
In the example, we are loading some data from the database, more precisely the cities from Adventure Works, ordered by name, before encountering "Boston". The following SQL query is used:
The data is loaded by
ListCities()
method which returns anIEnumerable<string>
. Here is whatforeach
looks like:Let's rewrite it with a
for
, assuming that both are interchangeable:Both return the same cities, but there is a huge difference.
foreach
,ListCities()
is called one time and yields 47 items.for
,ListCities()
is called 94 times and yields 28153 items overall.What happened?
IEnumerable
is lazy. It means that it will do the work only at the moment when the result is needed. Lazy evaluation is a very useful concept, but has some caveats, including the fact that it's easy to miss the moment(s) where the result will be needed, especially in the cases where the result is used multiple times.In a case of a
foreach
, the result is requested only once. In a case of afor
as implemented in the incorrectly written code above, the result is requested 94 times, i.e. 47 × 2:Every time
cities.Count()
is called (47 times),Every time
cities.ElementAt(i)
is called (47 times).Querying a database 94 times instead of one is terrible, but not the worse thing which may happen. Imagine, for example, what would happen if the
select
query would be preceded by a query which also inserts a row in the table. Right, we would havefor
which will call the database 2,147,483,647 times, unless it hopefully crashes before.Of course, my code is biased. I deliberately used the laziness of
IEnumerable
and wrote it in a way to repeatedly callListCities()
. One can note that a beginner will never do that, because:The
IEnumerable<T>
doesn't have the propertyCount
, but only the methodCount()
. Calling a method is scary, and one can expect its result to not be cached, and not suitable in afor (; ...; )
block.The indexing is unavailable for
IEnumerable<T>
and it's not obvious to find theElementAt
LINQ extension method.Probably most beginners would just convert the result of
ListCities()
to something they are familiar with, like aList<T>
.Still, this code is very different from the
foreach
alternative. Again, it gives the same results, and this time theListCities()
method is called only once, but yields 575 items, while withforeach
, it yielded only 47 items.The difference comes from the fact that
ToList()
causes all data to be loaded from the database. Whileforeach
requested only the cities before "Boston", the newfor
requires all cities to be retrieved and stored in memory. With 575 short strings, it probably doesn't make much difference, but what if we were retrieving only few rows from a table containing billions of records?So what is
foreach
, really?foreach
is closer to a while loop. The code I previously used:can be simply replaced by:
Both produce the same IL. Both have the same result. Both have the same side effects. Of course, this
while
can be rewritten in a similar infinitefor
, but it would be even longer and error-prone. You're free to choose the one you find more readable.Want to test it yourself? Here's the full code:
And the results:
LINQ vs. traditional way
As for LINQ, you may want to learn functional programming (FP) - not C# FP stuff, but real FP language like Haskell. Functional languages have a specific way to express and present the code. In some situations, it is superior to non-functional paradigms.
FP is known being much superior when it comes to manipulating lists (list as a generic term, unrelated to
List<T>
). Given this fact, the ability to express C# code in a more functional way when it comes to lists is rather a good thing.If you're not convinced, compare the readability of code written in both functional and non-functional ways in my previous answer on the subject.