tags:

views:

500

answers:

9

Hi,

I have a foreach block where I want to plot out for trace-debug purposes the index of the step inside the foreach. As a C# newbie I do it as follows:

int i = 1;

foreach (x in y)
{
    ... do something ...
    WriteDebug("Step: "+i.ToString());
    i++;
}

I wondered if there's any way to get the value of the current step's index without explicitly creating a variable for that purpose.

EDIT: To clarify, I'm obviously familiar with the option of a for loop, however it's not an array I'm going through but rather an unordered collection. The reason for the numbering is just for the purpose of showing progress in the debug level and nothing else.

Thanks

+14  A: 

No, there is not.

This is an instance where you're better off using a basic for loop

for(int i = 0; i < y.Count; i++)
{
}

rather than a for each loop

EDIT: In response to askers clarification.

If you're iterating through an enumerator with no size property (such as length or count), then your approach is about as clear as you can get.

Binary Worrier
Please see my clarification. A for loop does not work me. Working with foreach is far more important for me than plotting the numbers right... Thanks
Bab Yogoo
Thanks for the enlightened answer :)
Bab Yogoo
+1  A: 

No, there is no way to get that inside a foreach-loop. For that case you should use a for-loop or, as you mentioned, explicitly create a variable for counting.

bbohac
+3  A: 

No, there's no implicit "counter" inside a foreach loop, really.

What the foreach loop does behind the covers is create an IEnumerator and then loop over the items one by one, calling the .MoveNext() method on the IEnumerator interface.

There's (unfortunately?) no counter variable exposed on the IEnumerator interface - only .Reset() and .MoveNext() methods and a Current property (returning the current item)

Marc

marc_s
If he needs and wants to keep the foreach, then yes, having a separate `int` variable as the index is the only, and the right, thing to do, no?
marc_s
Macr, apologies but I saw your answer before I saw the askers update to their question. I have since deleted my unnecessarily sarcastic comment. A thousand apologies.
Binary Worrier
OK, no sweat, Binary Worrier!
marc_s
A: 

If you absolutely need the index, go with a traditional for loop instead.

for (int i = 0; i < y.Count; i++)
{
    WriteDebug("Step: "+i.ToString());    
}
MartinHN
Please see my clarification on the subject, thanks
Bab Yogoo
+2  A: 

A foreach uses the IEnumerator interface, which has a Current property, and MoveNext and Reset methods.

Current returns the object that Enumerator is currently on, MoveNext updates Current to the next object.

There isn't a concept of index in foreach and we won't be sure of the order of enumeration.

You will have to either apply a variable for that or use a for loop instead of that.

I would prefer use a for lop instead of tracking this using a variable.

rahul
+12  A: 

Contrary to a few other answers, I would be perfectly happy to mix foreach with a counter (as per the code in the question). This retains your ability to use IEnumerable[<T>] rather than requiring an indexer.

But if you want, in LINQ:

    foreach (var pair in y.Select((x,i) => new {Index = i,Value=x})) {
        Console.WriteLine(pair.Index + ": " + pair.Value);
    }

(the counter approach in the question is a lot simpler and more effecient, but the above should map better to a few scenarios like Parallel.ForEach).

Marc Gravell
+1 Very cool but seems like overkill for my app :)
Bab Yogoo
Marc, your select statement above is wrecking me head :) (I'm no linq expert). Where's the magic that increments i?
Binary Worrier
Inside Select ;-p Basically, you need to give this overload of Select a `Func<TInput,int,TOutput>` - i.e. given the value and the index, do something... See MSDN: http://msdn.microsoft.com/en-us/library/bb534869.aspx
Marc Gravell
Good stuff, thanks :)
Binary Worrier
+1  A: 

It depends upon the actual type of the enumerator you are for looping over. However, a lot of collections have the IndexOf method

        ArrayList arrayList = new ArrayList();

        arrayList.Add("A");
        arrayList.Add("B");
        arrayList.Add("C");

        foreach (string item in arrayList)
        {
            int i = arrayList.IndexOf(item);
        }

Of course this doesn't work if you have duplicate items in your list. It's also not the most efficient solution. I'd stick with your original one and just keep a variable to keep track of the index.

Robin Day
I'm interested in the order of "steps" in the foreach. Does the "IndexOf" necessarily return the same order over which the foreach will go though? (even if the values are unique)
Bab Yogoo
I do not know. You will have to try this on your COM object if you have the IndexOf method. However, on looking at your other comments I think I'd still stick with your original code.
Robin Day
Thanks. +1 for the idea...
Bab Yogoo
+2  A: 

Your approach is about as clear as it gets. However, you might want to note the i++ part is not actually related to the core functionality of the loop (no count/length/other parameters involved). As such, you might want to consider moving both the writeDebug and the i++ into a separate method/class (updateProgress()), and simply call that from the loop.

Paul-Jan
The downside of this is that you'd have to use `ref int i`, but the upside is that you could mark the second method as `[Conditional("DEBUG")]` so that it doesn't get included in release builds...
Marc Gravell
A: 

I don't know what exactly "y" is in your example, but perhaps you could write it like this:

  foreach (var x in y.WithIndex())
  {
    WriteDebug(String.Format("Step: {0}", x.Index));
  }

Provided that you add following extension class to your project:

  public static class Extensions
  {
    public static IEnumerable<IndexValuePair<T>> WithIndex<T>(this IEnumerable<T> source)
    {
      if (source == null) throw new ArgumentNullException("source");
      var position = 0;
      foreach (T value in source)
      {
        yield return new IndexValuePair<T>(position++, value);
      }
    }
  }

  public class IndexValuePair<T>
  {
    public IndexValuePair(Int32 index, T value)
    {
      this.index = index;
      this.value = value;
    }

    private readonly Int32 index;
    public Int32 Index
    {
      get { return index; }
    }

    private readonly T value;
    public T Value
    {
      get { return value; }
    }
  }

HTH, Dejan

Dejan Stanič