views:

896

answers:

7

I hope i don't get slammed for asking something so basic. I could Google for the answer, but I want to hear something that's not from a textbook.

I'm writing a unit test to verify that my IDictionary Keys are sequential.

Since the Keys property is an ICollection<T>, I want to enumerate over the collection and print the Key values to the console.

When attempting to print the Key values using a simple for loop:

for (int i = 0; i < unPivotedData.Count; i++)
{
    Console.WriteLine(unPivotedData.Keys[i]);
}

I received the following compile error:

Cannot apply indexing with [] to an expression of type 'System.Collections.Generic.ICollection<int>'

However, when I used the foreach loop:

foreach(int key in unPivotedData.Keys)
{
    Console.WriteLine(key);
}

Everything worked just fine.

I understand what an indexer does and how it's implemented, but how does the foreach work? I don't understand how the foreach can work yet the for results in a compiler error.

Am I missing a fundamental of Enumeration here?

Cheers!

EDIT: Furthermore, is there a performance difference between the two? I know I cannot use for with an IDictionary but if I'm using an IList, I can. Does the for move quicker than the foreach or is the performance gain negligible

+7  A: 

foreach simply needs IEnumerable; whereas for(a,b,c) (in your case) requires an indexer property.

When you call foreach on an object that implements IEnumerable, under the hood it calls GetEnumerator(), which returns an IEnumerator object. That object implements a few members like MoveNext() and Current. Each iteration of the foreach is actually calling MoveNext(), which returns true if the enumerator can move forward, and false if it can't (reached the end).

The key here is that an object which implements IEnumerable does not know how many items it has, the index of the item it's on, etc. All it knows is that it can move forward to the next item, return that as the current item, until it runs out of items.

A for(a,b,c) loop, on the other hand, will essentially loop forever - i.e. it is not constrained by the collection you might happen to be iterating over (although in practice it usually is). At the most basic level, it executes C once each time, and checking if B is true. If B is true, it will loop again. If it's false, it will stop. Each time it loops, inside the loop you are probably calling object[number], which if your object does not have such a property, will of course fail.

The key here is that an object with an indexer supports random access - that means you can, at any point, call [arbitrary index] and grab that one. Contrast this with IEnumerable, which again can only access the current item.

Rex M
+1  A: 

Foreach uses the IEnumerable interface. See it's definition in help and you'll get idea.

csharptest.net
A: 

A foreach goes through the IEnumerable interface, which just provides a custom Enumerator. See http://msdn.microsoft.com/en-us/library/system.collections.ienumerable.aspx for info.

Basically, it acts as a custom Linked-list sort of thing, with a 'current' and a 'move next'.

Erich
A: 

Read Using foreach with Collections from the C# documentation.

John Fisher
+1  A: 

As others have stated the foreach uses the Enumerator of the collection, so

foreach(int key in unPivotedData.Keys)
{
    Console.WriteLine(key);
}

translate into something like:

IEnumerator enumerator = unPivotedData.Keys.GetEnumerator()
while(enumerator.MoveNext())
{
  Console.WriteLine(enumerator.Current);
}

as you can see there's no indexing in that. Indexing is a very different beast from iterator that can be used for iteration purposes (as you're trying in you for loop) but an iterator cannot be used for indexing only iterating

Rune FS
+4  A: 

The Keys collection is not indexed because by definition of a dictionary, ordering of the elements is not guaranteed. Which, unfortunately, also means that the purpose of the unit test you're writing is fundamentally flawed.

Jon Seigel
+3  A: 

You can think of the difference between IList<T> (which supports indexing) and IEnumerable<T> like this:

If I have a room with objects scattered all over the floor, and I ask you to retrieve for me all the objects in the room, you can just go in, start picking up objects in no particular order and tossing them to me. Order does not matter; what matters is that you go through all of the items. The objects in the room, in this sense, could be abstractly thought of as implementing the IEnumerable interface. Then if I throw all the items back into the room and ask you to do it again, when you go through them the second time there's no guarantee you'll pick them up in the same order. Essentially, this is because it doesn't make sense to ask, "What is the nth item in the room?"

This is an important note about IEnumerable: though I've never seen a case where foreach doesn't use the same order on every call, it doesn't have to.

On the other hand, if I put objects into a room in a specific order and I ask you to go in and pick them up in the same order in which I placed them, then it would make sense to assign a number to each object. The question, "What is the nth item I placed in the room?" actually makes sense in this context. Thus, in this scenario the objects in the room may be thought of as implementing the IList interface.

Dan Tao