The authors of that article are making an apples-verses oranges comparison.
When one uses a local variable to store the count of a list and then iterate over the list using the local variable as the upper bounds, they're neglecting to consider that the list can change while iterating over it using for(), including adding or removing elements. In contrast to that, when one uses the Count property of the list directly in a for() loop, it is evaluated on each iteration. That means that if the size of the list changes while you are iterating over it, it will iterate over the elements added to it.
public static void ListIteration()
{
List<int> list1 = Enumerable.Range(0, 100).ToList();
List<int> list2 = new List<int>();
CollectionsMarshal.SetCount(list2, list1.Count);
Span<int> span1 = CollectionsMarshal.AsSpan(list1);
Span<int> span2 = CollectionsMarshal.AsSpan(list2);
span1.CopyTo(span2);
/// Using a variable as the upper range:
///
int count = list1.Count;
int total = 0;
for(int i = 0; i < count; i++)
{
if(i < 10)
list1.Add(i + 101);
total += list1[i];
}
Console.WriteLine($"list1.Count = {list1.Count} Total = {total}");
/// Using the list's Count property as the upper range,
/// adding elements during iteration, all elements in
/// the list are iterated over, rather than only those
/// that were in the list at the start of iteration:
total = 0;
for(int i= 0; i < list2.Count; i++)
{
if(i < 10)
list2.Add(i + 101);
total += list2[i];
}
Console.WriteLine($"list2.Count = {list2.Count} Total = {total}");
}
While removing elements from the list below the position of the current iterator variable will really screw things up and likely cause a failure, it is not too uncommon to append elements to a list while iterating over it, since all of the elements added will be iterated over as well. That is a common practice in cases where recursive patterns are converted to iterative patterns.
I don't have any comments on the performance observations other than that I know that Enumerable.ToArray() performance has improved dramatically since the early days of .NET.
Using a lambda for iterating the LINQ way, like in Array.Foreach(array, item => ...) or list.ForEach(item => ...) is unsurprisingly, way slower than regular for and foreach loops....
The ForEach() methods of Array and List<T> aren't Linq. They just use delegates as most Linq methods require. Invoking a delegate has overhead (it uses callvirt, which makes it roughly equivalent to invoking a virtual method of an instance of a class), and doing that once for each element in an array or list definitely isn't going to be competitive with iterating using for() or foreach(), so the results they observed really isn't surprising, and because Linq is also delegate-intensive, it is also slower than using language constructs. With Linq, you not only have the overhead of delegates, you also have the overhead of IEnumerable<T>, which requires two method invocations to retrieve each element. And, add to those the overhead of non-static variable captures in delegates.