Pitfalls and Best Practices to prevent them #1: Order of the elements when iterating through a collection using foreach

Example

List<string> list = //Fill it;
//...
int index = 0;
foreach (string s in list)
    Console.WriteLine("Item \"{0}\" - Index: {1}", s, index++);

What does it do?

The example is kept quite simple: A generic List<string> gets filled with some items (—> Comment).

Later, all items in it and their indices are written to the Console.

Note: The foreach loop internally uses IEnumerable.GetEnumerator / IEnumerable<T>.GetEnumerator.

Doku says

MSDN (IEnumerator.GetEnumerator)

Initially, the enumerator is positioned before the first element in the collection. […] Therefore, you must call the MoveNext method to advance the enumerator to the first element of the collection before reading the value of Current.

[…] MoveNext sets Current to the next element.

If MoveNext passes the end of the collection, the enumerator is positioned after the last element in the collection and MoveNext returns false. […]

C# Language Specification (8.8.4: The foreach statement)

The order in which foreach traverses the elements of an array, is as follows: For single-dimensional arrays elements are traversed in increasing index order, starting with index 0 and ending with index Length – 1. For multi-dimensional arrays, elements are traversed such that the indices of the rightmost dimension are increased first, then the next left dimension, and so on to the left.

Problem

Well, it’s a question of interpretation: Does MoveNext set to the next element in the collection or only to the next element (regardless to its position in the collection)? Only the behavior when dealing with arrays is specified unambiguously.

There could be a class implementing (for whatever the reason) GetEnumerator like follows:

private List<string> innerList;
//...
public IEnumerator GetEnumerator()
{
    for (int i = this.innerList.Count - 1; i >= 0; i--)
        yield return this.innerList[i];
}

Using this code together with the first one causes significant problems: The displayed indices are wrong now.

And there are classes that behave different; Class" href="http://msdn.microsoft.com/en-us/library/xfhwa508.aspx" target=_blank>Dictionary<,> is a prime example:

For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair<TKey, TValue> structure representing a value and its key. The order in which the items are returned is undefined.

How to solve?

Don’t use foreach loops when you need the index of an element. (And also don’t use IndexOf in a foreach loop – this, however, has performance reasons).

Use a plain old for-loop instead ;-)

List<string> list = //Fill it;
//...
int index = 0;
for (int index = 0; index < list.Count; index++)
{
    string s = list[index]; //Of course you don't have to introduce a variable
    Console.WriteLine("Item \"{0}\" - Index: {1}", s, index++);
}

However, this is no solution if you are using a dictionary; thanks to SeeQuark for informing me about this. In this case, if you really need a well-definied order, you’ll have to add an index to the elements in it.

Generalization / Best Practise:

Do not rely on the order elements will be returned by an iterator. As the foreach-loop uses one, the same applies to it. Foreach-loops using a counter variable in order to determine the index of each element should be replaced with for-loops.

DotNetKicks Image
About these ads

2 Responses to “Pitfalls and Best Practices to prevent them #1: Order of the elements when iterating through a collection using foreach”

  1. SeeQuark Says:

    Generally, you have seen quite.
    However, you said, the Dictionary would be an example for a Enumerator, wich has an undefined order. That’s right.
    But how do you will use the for-loop instead the foreach-loop in a Dictionary?

  2. winsharp93 Says:

    Yippee! The first comment in my blog :-D

    >> But how do you will use the for-loop instead the foreach-loop in a Dictionary?
    Yes, you are right: The solution does not really apply to this example. I amended this by adding a note.
    Thanks!


Comments are closed.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: