Why Can't You Code?
The highest form of ignorance is when you reject something you don't know anything about.
The highest form of ignorance is when you reject something you don't know anything about.
Nov 13th
I’ve seen various ways of handling the lack of support for an index in the foreach () construct over the years.
By far the most used strategy is to declare an index yourself and increment it at the end of every loop iteration. Let me demonstrate:
static void Main(string[] args)
{
var range = Enumerable.Range(0, 100);
int index = 0;
foreach (int number in range)
{
Console.WriteLine(index + " " + number);
index++;
}
// Output:
// 0 0
// 1 1
// 2 2
}
However, this becomes more of a problem if the code inside your loop conditionally breaks out of the loop, or if you need to use continue; to end the iteration early, requiring you to keep track of all of your exit points.
static void Main(string[] args)
{
var range = Enumerable.Range(0, 100);
int index = 0;
foreach (int number in range)
{
if (number < 50) continue; // <- oops, the index will be out of sync now. shouldn't have forgotten to increment it
Console.WriteLine(index + " " + number);
index ++;
}
// Output:
// 0 50 (should be 50 50 – remember, this would be the 50th loop iteration, but it was ended early, before the index got a chance to be incremented)
// 1 51 (should be 51 51)
// 2 52 (should be 52 52)
}
You can get rid of these problems with a little known overload for the Linq Enumerable.Select() method which will supply the much needed index. Behold the uglyness:
static void Main(string[] args)
{
var range = Enumerable.Range(0, 100);
foreach (var c in range.Select((value, index) => new { Index = index, Value = value }))
{
if (c.Value < 50) continue; // <- the index will still be correct despite the early exit
Console.WriteLine(c.Index + " " + c.Value);
}
// Output:
// 50 50 (correct)
// 51 51
// 52 52
}
Damn code, you scary! (if you don’t want to get ate, see the elegant solution right below the video)
Being someone who doesn’t particularly enjoy uglyness, I decided to wrap this into an extension method for IEnumerable<T>, so I can write this beautiful beautiful code below:
static void Main(string[] args)
{
var range = Enumerable.Range(0, 100);
foreach (var c in range.WithIndex())
{
if (c.Index % 2 == 0) continue; // <- the index will still be correct despite the early exit
Console.WriteLine(c.Index + " " + c.Value);
}
}
You want your index to go backwards? Easy, an overload does all the dirty work, you can specify the value to start at and the step as parameters. The step can even be negative if you want!
foreach (var c in range.WithIndex(range.Count(), –1)) { }
Here’s the code listing for the extension method, enjoy:
public static class EnumerableWithIndexExtension
{
public static IEnumerable<ValueWithIndex<T>> WithIndex<T>(this IEnumerable<T> enumerable)
{
return enumerable.Select((value, index) => new ValueWithIndex<T>(value, index));
}
public static IEnumerable<ValueWithIndex<T>> WithIndex<T>(this IEnumerable<T> enumerable, int startAt, int step)
{
return enumerable.Select((value, index) => new ValueWithIndex<T>(value, startAt + index * step));
}
public class ValueWithIndex<T>
{
public int Index { get; private set; }
public T Value { get; private set; }
public ValueWithIndex(T value, int index)
{
Value = value;
Index = index;
}
}
}