12

I have a foreach loop that needs converting to a for or while loop. My loop looks like this:

foreach (Item item in Items)
{
    // some stuff
}

What is the equivalent for or while loop?

I think I need to use GetEnumerator to get an IEnumerator<Item>, but I don't know how to use it properly. Items isn't a list, otherwise I'd use indexing.

Kendall Frey
  • 43,130
  • 20
  • 110
  • 148
  • is there any particular reason for why you need to convert it? – Massimiliano Peluso Aug 21 '12 at 15:45
  • @MassimilianoPeluso I need do extra stuff that won't work in a `foreach`, so I need to know how to use GetEnumerator. – Kendall Frey Aug 21 '12 at 15:47
  • @KendallFrey: What stuff out of interest and will it work with an enumerator? You might be best off jsut doing what Ryan suggested in his (now deleted) answer and just using the count to loop. – Chris Aug 21 '12 at 15:48
  • 2
    If we come to perfect replacement it just may be so perfect that it does have the same drawback that you wanted to surpass in the first place. So what is the original drawback? – Dialecticus Aug 21 '12 at 15:48
  • @KendallFrey why don't you use the Count() method then in the classic for each and access the item by index? Am I missing something? – Massimiliano Peluso Aug 21 '12 at 15:49
  • Not to sound thick, but if you can't do something within the standard `foreach` construct, how will expanding it out to the form using `GetEnumerator` be any better? – mclark1129 Aug 21 '12 at 15:50
  • Okay, I lied. I don't need the exact same code. But if I know how to write the for loop, I know how to write my code to requirements. – Kendall Frey Aug 21 '12 at 15:50
  • @MassimilianoPeluso 1) Not every enumerable supports indexing 2) Not every enumerable supports multiple evaluations. – CodesInChaos Aug 21 '12 at 16:11
  • There are some uses for manual enumeration. For example implementing `Enumerable.Zip` without it is ugly. – CodesInChaos Aug 21 '12 at 16:18

3 Answers3

25

In the simplest case(no disposing etc.) you can use:

var enumerator = Items.GetEnumerator();// `var` to take advantage of more specific return type
while(enumerator.MoveNext())
{
  Item item = enumerator.Current;
  ...
}

For the exact rules check the C# specification 8.8.4 The foreach statement.

A foreach statement of the form

foreach (V v in x) embedded-statement

is then expanded to:

{
  E e = ((C)(x)).GetEnumerator();
  try {
     V v;
     while (e.MoveNext()) {
        v = (V)(T)e.Current;
                embedded-statement
    }
  }
  finally {
     … // Dispose e
  }

}

(Quoted from the C# Language Specification Version 4.0)

The types using here are: "a collection type C, enumerator type E and element type T". E is the return type of GetEnumerator, and not necessarily IEnumerator<V> since foreach uses duck typing. The spec also describes a number of corner cases and how to infer these types, but those details are probably not relevant here.

In C# 5 the declaration of v will be moved into the while loop to get more intuitive closure semantics.

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • 1
    The change made in C# 5.0 is explained here, in case anyone missed it: http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach/8899347#8899347 – BoltClock Aug 21 '12 at 15:53
  • Can you explain the types/variables `E`, `C` and `T`? They don't come from the original `foreach`. – Kendall Frey Aug 21 '12 at 15:54
  • `E` is the enumerator, `C` the collection type and `T` the element type. – CodesInChaos Aug 21 '12 at 15:58
  • Great. I understand `E` and `T`, but I don't get why `((C)(x))` is any different than `(x)`. – Kendall Frey Aug 21 '12 at 16:00
  • 1
    I think that's for the case where there is no `GetEnumerator` method, but `IEnumerator.GetEnumerator` was implemented explicitly. The casts to `T` and `C` are just technicalities related to corner cases. The cast to `V` on the other hand is a clearly visible effect: `foreach` looks at explicit conversions from `T` to `V`, and not just implicit conversions. – CodesInChaos Aug 21 '12 at 16:04
  • I hope that when they make the change in C# 5.0, that it matches the specification outlined in the C# 5.0 book that Jon Skeet has already written :) - http://meta.stackexchange.com/a/9174/193560 – mclark1129 Aug 21 '12 at 16:04
  • The use of `Dispose` should be considered required, not optional, except in cases where the concrete type of the enumerator is *known* not to require it. Some iterators may behave badly if `Dispose` is not called. – supercat Aug 21 '12 at 18:46
5

If you're going to use a for loop, it generally means there's some way of quickly accessing the n-th item (usually an indexer).

for(int i = 0; i < Items.Count; i++)
{
  Item item = Items[i]; //or Items.Get(i) or whatever method is relevant.
  //...
}

If you're just going to access the iterator, you usually just want to use a foreach loop. If, however, you can't, this is usually the model that makes sense:

using(IEnumerator<Item> iterator = Items.GetEnumerator())
while(iterator.MoveNext())
{
  Item item = iterator.Current;
  //do stuff
}

you could, technically, do this in a for loop, but it would be harder because the construct just doesn't align well with this format. If you were to discuss the reason that you can't use a foreach loop we may be able to help you find the best solution, whether or not that involves using a for loop or not.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Correct use of an enumerator requires calling `IDisposable` upon it before abandoning it, unless the *concrete type* of the enumerator is known not to require cleanup. – supercat Aug 21 '12 at 18:50
  • 2
    @supercat Which is why I wrap the iterator in a `using` block, which properly disposes of it... – Servy Aug 21 '12 at 18:56
2

This is an equivalent in a for-loop

for (IEnumerator i = Items.GetEnumerator(); i.MoveNext(); )
{
    Item item = (Item)i.Current;
    // some stuff
}
Jason
  • 1,385
  • 9
  • 11
  • 1
    If you don't call MoveNext first, accessing Current will throw an InvalidArgumentException. It doesn't skip anything for me. – Jason Aug 21 '12 at 16:11
  • @AustinSalonen I tried it, and it worked. Yours creates an extra item. – Kendall Frey Aug 21 '12 at 16:11
  • @AustinSalonen It doesn't. I just tested it. – CodesInChaos Aug 21 '12 at 16:13
  • 2
    The above neglects to call `Dispose` on the enumerator. While there are some enumerator types which do not implement `IDisposable`, one should call `IDisposable.Dispose` on an enumerator unless one knows that the *concrete type* of the enumerator does not require cleanup. – supercat Aug 21 '12 at 18:48