Sunday, November 23, 2008

Selecting or filtering on the sequence number in Linq

A few weeks ago I was writing a Linq query where I needed each items sequence number (or index) as a part of the filter expression in a Where operation. To simplify the actual problem let's say I needed to filter out only the odd items. One (wrong) way to do this would be:
int i = 0;
var filtered = input.Where(x => i++ % 2 == 1);
This will appear to work at first but it only works correct the first time the filtered sequence is iterated. I will not get into this to deep here, but it has to do with the lambda updating the outer variable which is a bad idea.
Fortunately there is a correct (and easier) way to do this. As it turns out the Where extension method has an extra overload that excepts a lambda function with two input parameters. When you use this overload the lambda receives an extra input parameter which indicates the sequence number of the current item. The correct code looks like this.
var filtered = input.Where((x, i) => i % 2 == 1);
As I wanted to write the rest of the query in Linq comprehension syntax (yes it does have some advantageous sometimes), I wanted to write something like
var filtered = from x in input
               // other Linq stuff           
               where ???? % 2 == 1
               select x;
Comprehension syntax however does not have a built in way to refer to the sequence number (as far as I could find out in 15 min with Google). Bummer :-(
I did find that the Select method has an overload just like the Where method, that accepts a lambda with a sequence number parameter. This allows the sequence number to be used in the query's output.
var withSequenceNumber = input.select((item, index) => new {item, index})
Just like in the where clause, it is not possible to use the sequence number in comprehension syntax's select clause. It is however possible to mix normal C# syntax and comprehension syntax and make the sequence number available to all the comprehension syntax operators:
var filtered = from x in input.select((item, index) => new {item, index})
              // other Linq stuff           
              where x.index %2 == 1
              select x.item;
By first selecting each item along with its sequence number into an anonymous type, the rest of the query can refer to the sequence number as x.index and the item itself as x.item. As an extra bonus the original sequence numbers of the items before filtering are still available after filtering, so they could be used in the select clause to be presented in the ouput.

No comments:

Post a Comment