Tuesday, March 08, 2011

Adding the sequence number to a LINQ query

LINQ queries are a powerful way to keep your code expressive and by using differed execution, they are quick. But what if you need the value of the index which LINQ used whilst building the projection? I had this issue and found the solution was to use the Select method which accepts a Func<TSource, int, TResult> for the selector.

With a for loop this is simple to accomplish as the index value is available in each iteration as it is controlling the loop:
var lines = new[]
{
"Line A",
"Line B",
"Line C"
};
var dict = new Dictionary<string, string>();
for(int i=0; i <= lines.GetUpperBound(0); i++)
{
dict.Add(string.Format("Address {0}", i), lines[i]);
}
foreach (var dictionary in dict)
{
Console.WriteLine("{0} : {1} ",dictionary.Key, dictionary.Value);
}
view raw example1.cs hosted with ❤ by GitHub
This code prints this to the console:

Key: Address 0 - Value: Line A
Key: Address 1 - Value: Line B
Key: Address 2 - Value: Line C
view raw example2.cs hosted with ❤ by GitHub

Creating a LINQ query with the indexer in the projection is not as obvious. My first attempt was to use the Count() property of the line parameter in the projection.

var lines = new[]
{
"Line A",
"Line B",
"Line C"
};
var query = lines.Select(line => new
{
Prop = string.Format("Address {0}", line.Count()),
Value = line
});
foreach (var prop in query)
{
Console.WriteLine("Prop: {0} - Value: {1} ",prop.Prop, prop.Value);
}
view raw example3.cs hosted with ❤ by GitHub

But when run to the console the problem became apparent

Prop: Address 6 - Value: Line A
Prop: Address 6 - Value: Line B
Prop: Address 6 - Value: Line C
view raw example4.cs hosted with ❤ by GitHub

In the projection, line.Count() is returning the string length of each line in the array. First attempts are always a good way to discover how something could work

Fortunately the LINQ Select Method has two overloads. They both iterate over an IEnumerable<T> but it is the delegate for the selector which differs. The code above uses the first delegate which should be Func<TSource, TResult>. The second overload expects a Func<TSource, int, TResult>. Here, the parameter used for the int32 is assigned the current value for the index of the sequence.

The first code example can now be changed to something more expressive

var lines = new[]
{
"Line A",
"Line B",
"Line C"
};
var query = lines.Select((line, index) =>; new
{
Prop = string.Format("Address {0}", index),
Value = line
});
foreach (var prop in query)
{
Console.WriteLine("Prop: {0} - Value: {1} ",prop.Prop, prop.Value);
}
view raw example5.cs hosted with ❤ by GitHub

Running this code displays the following in the console

Prop: Address 0 - Value: Line A
Prop: Address 1 - Value: Line B
Prop: Address 2 - Value: Line C
view raw example6.cs hosted with ❤ by GitHub

Many of the LINQ methods have this overload for the selector. Using it I have been able to continue using LINQ to specify how I want to transform the array. This means my code is more expressive. Plus, the LINQ query is quick as the projection will only be populated when the foreach loop is executed to display the result.