Phil Haack has an interesting article about creating a better Razor foreach loop. It’s a useful solution when one needs to know the current index of an item enumerating a sequence, but I feel the concepts presented can be expanded upon outside of the scope of his article.
In the article, Haack presented a wrapper class called IndexedItem<TModel>. I’m going to change the name of this class to Indexed<T>, expand it slightly, and add a static factory method to take advantage of generic inference.
public class Indexed<T>
{
public Indexed(int index, T value)
{
Index = index;
Value = value;
}
public int Index { get; private set; }
public T Value { get; private set; }
public Indexed<U> Select<U>(Func<T, U> selector)
{
return Indexed.Create(Index, selector(Value));
}
}
public static class Indexed
{
public static Indexed<T> Create<T>(int index, T item)
{
return new Indexed<T>(index, item);
}
}
This wrapper allows us to attach state information to a value. In this case, we’re explicitly declaring that the state information is an Index. The Select method allows us to use LINQ to modify the inner value from one type to another using query expressions. Since the value is exposed, this can be accomplished through other means, but the Select method allows the syntax to be cleaner in certain scenarios.
The next thing to do is allow for the easy projection of any sequence to an indexed sequence. This can be accomplished with an extension method on IEnumerable<T>.
public static IEnumerable<Indexed<T>> ToIndexed<T>(this IEnumerable<T> source)
{
int index = 0;
foreach (T t in source)
{
yield return Indexed.Create(index, t);
index++;
}
}
That’s all there is to it. Here’s a simple test to ensure it works.
[TestMethod]
public void IndexedTest()
{
var strings = new[] {"One", "Two", "Three", "Four"};
var indexed = strings.ToIndexed();
var ordered = from s in indexed
orderby s.Value
select s;
Assert.AreEqual(0, indexed.First().Index);
Assert.AreEqual("Four", ordered.First().Value);
Assert.AreEqual(3, ordered.First().Index);
}
With an indexed sequence, you can change the order yet retain the state for its original index. This can be useful for binding to a sequence without retaining another copy to retain original index information… or worse, junking up business objects with data required only for the presentation layer.
This sample code is available at kodefu.codeplex.com.