9

I run into this often during programming where I want to have a loop count index inside of a foreach and have to create an integer, use it, increment, etc. Wouldn't it be a good idea if there was a keyword introduced that was the loop count inside of a foreach? It could also be used in other loops as well.

Does this go against the use of keywords seeing as how the compiler would not allow this keyword used anywhere but in a loop construct?

8
  • 7
    You can do it yourself with an extension method, see the answer from Dan Finch: stackoverflow.com/a/521894/866172 (not the best rated answer, but I think it's the "cleaner" one)
    – Jalayn
    Jun 14, 2012 at 12:55
  • Perl and stuff like $_ comes to mind. I hate that stuff. Jun 14, 2012 at 13:23
  • 2
    From what I know about C#, it has many context-based keywords, so this wouldn't "go against the use of keywords". As pointed out in an answer below however, you probably just want to use a for () loop.
    – Craige
    Jun 14, 2012 at 13:58
  • @Jalayn Please post that as an answer. It is one of the best approaches.
    – Apoorv
    Jun 14, 2012 at 14:08
  • @MonsterTruck: I don't think he should. The real solution would be to migrate this question to SO and close it as a duplicate of the question Jalayn links to.
    – Brian
    Jun 14, 2012 at 15:50

8 Answers 8

54

If you need a loop count inside a foreach loop why don't you just use a regular for loop. The foreach loop was intended to make specific uses of for loops simpler. It sounds like you have a situation where the simplicity of the foreach is no longer beneficial.

10
  • 1
    +1 - good point. It's easy enough to use IEnumerable.Count or object.length with a regular for loop to avoid any problems with figuring out the number of elements in the object.
    – user53019
    Jun 14, 2012 at 13:47
  • 11
    @GlenH7: Depending on the the implementation, IEnumerable.Count could be expensive (or impossible, if it's a once only enumeration). You need to know what the underlying source is before you could safely do that. Jun 14, 2012 at 13:56
  • 2
    -1: Bill Wagner in More Effective C# describes why one should always stick to foreach over for (int i = 0…). He has written a couple of pages but I can summarise in two words --compiler optimisation on iteration. Ok that wasn't two words.
    – Apoorv
    Jun 14, 2012 at 14:05
  • 13
    @MonsterTruck: whatever Bill Wagner wrote, I have seen enough situations like the one described by the OP, where a for loop gives better readable code (and theoretical compiler optimization is often premature optimization).
    – Doc Brown
    Jun 14, 2012 at 14:22
  • 1
    @DocBrown This is not as simple as premature optimisation. I have myself identified bottlenecks in the past and fixed them using this approach (but I'll admit I was dealing with thousands of objects in the collection). Hope to post a working example soon. Meanwhile, here is Jon Skeet. [He says performance improvement won't be significant but that depends much on the collection and the number of objects].
    – Apoorv
    Jun 14, 2012 at 15:03
37

You can use anonymous types like this

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

This is similar to Python's enumerate function

for i, v in enumerate(items):
    print v, i
9
  • +1 Oh... light bulb. I like that approach. Why have I never thought of it before?
    – Phil
    Jun 14, 2012 at 15:35
  • 17
    Whoever prefers this in C# over a classical for loop clearly has a weird opinion on readability. This may be a neat trick but I wouldn't allow something like that in production quality code. It's much noisier than a classical for loop and cannot be understood as fast.
    – Falcon
    Jun 14, 2012 at 17:31
  • 4
    @Falcon, this is an valid alternative IF YOU CAN'T USE an old fashion for loop (which needs an count). That's a few object's collections which I had to work around... Jun 14, 2012 at 18:42
  • 8
    @FabricioAraujo: Surely C# for loops don't require a count. As far as I know they are just the same as C's for loops, which means you can use any boolean value to end the loop. Such as a check for the end of the enumeration when MoveNext returns false.
    – Zan Lynx
    Jun 14, 2012 at 20:16
  • 1
    This has the added benefit of having all the code to handle the increment at the start of the foreach block instead of having to find it.
    – JeffO
    Mar 2, 2016 at 16:46
13

I'm just adding Dan Finch's answer here, as requested.

Don't give me points, give points to Dan Finch :-)

The solution is to write the following generic extension method:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

Which is used like so:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );
3
  • 2
    That's pretty clever, but I think just using a normal for is more "readable", especially if down the road someone who ends up maintaining the code may not be much of an ace in .NET and not too familiar with the concept of extension methods. I'm still grabbing this little bit of code though. :) Jun 14, 2012 at 15:12
  • 1
    Could argue either side of this, and the original question - I agree with the posters intent, there are cases where foreach reads better but an index is needed, yet I am always adverse to adding more syntactic sugar to a language if it isn't badly needed - I have exactly the same solution in my own code with a different name - strings.ApplyIndexed<T>(......) Jun 14, 2012 at 16:29
  • I agree with Mark Mullin, you can argue either side. I thought it was a pretty clever use of extension methods and it fitted the question so I shared it.
    – Jalayn
    Jun 14, 2012 at 16:56
5

I may be wrong about this, but I always thought that the main point of the foreach loop was to simplify iteration from the STL days of C++ i.e.

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

compare this to .NET's use of IEnumerable in the foreach

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

the latter is much simpler and avoids many of the old pitfalls. In addition, they seem to follow almost identical rules. For instance you can't change the the IEnumerable contents while inside the loop (which makes much more sense if you think of it the STL way). However, the for loop often has a different logical purpose than iteration.

4
  • 4
    +1: few people remember that foreach is basically syntactic sugar over the iterator pattern. Jun 14, 2012 at 13:36
  • 1
    Well there are some differences; pointer manipulation where present in .NET is hidden pretty deeply from the programmer, but you could write a for loop that looked very much like the C++ example: for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
    – KeithS
    Jun 14, 2012 at 16:34
  • @KeithS, not if you realize that everything you touch in .NET that isn't a structured type IS A POINTER, just with different syntax (. instead of ->). In fact, passing a reference type to a method is the same as passing it as a pointer and passing it by reference is passing it as a double pointer. Same thing. At least, that is the way a person who's native language is C++ thinks about it. Sort of like, you learn Spanish in school by thinking about how the language is syntactically related to your own native language. Jun 14, 2012 at 19:02
  • *value type not structured type. sorry. Jun 14, 2012 at 23:04
2

If the collection in question is an IList<T>, you can simply use for with indexing:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

If not, then you could use Select(), it has an overload that gives you the index.

If that's also not suitable, I think maintaining that index manually is simple enough, it's just two very short lines of code. Creating a keyword specifically for this would be an overkill.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}
1
  • +1, also, if you are using an index exactly once inside the loop, you could do something like foo(++i) or foo(i++) depending on which index is needed.
    – Job
    Jun 15, 2012 at 3:51
2

If all you know is that the collection is an IEnumerable, but need to keep track of how many elements you have processed so far (and thus the total when you're done), you can add a couple lines to a basic for loop:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

You can also add an incremented index variable to a foreach:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

If you want to make this look more elegant, you can define an extension method or two to "hide" these details:

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));
1

Scoping also works if you want to keep the block clean of collisions with other names...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }
-1

For this I use a while loop. Loops with for also work, and many people seem to prefer them, but I only like to use for with something that will have a fixed number of iterations. IEnumerables may be generated at run time, so, to my mind, while makes more sense. Call it an informal coding guideline, if you like.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.