May 27, 2009

LINQ Versus ForEach II

Since Hollywood just released  Night at the Museum II,  Transformers II, and Terminator II (squared), I thought I should get with the program and make a sequel to my original post. My approach here is going to show a fairly simple example and take it through various transformations to demonstrate how to take existing code and start thinking about it in LINQ.

The foreach or ForEach approach
Let's start with a foreach loop I would mostly leave alone.

                foreach (Widget widget  in bunch_of_widgets)
                    widget.SomeProperty = new_value;
This appeals to me a little better using the LINQ method syntax.

   bunch_of_widgets.ForEach( w => w.SomeProperty = new_value);

Of course if you get paid by the line of code you might not like this. However, the real reason I bring up this syntax is so that it helped me to stop thinking of LINQ as strictly a query language (despite that Q on the end).

Moving on, if I change the above example to test a conditional, the standard foreach would look like:

                foreach (Widget widget  in bunch_of_widgets)
                    if (widget.SomeTestProperty)  widget.SomeProperty = new_value;

   bunch_of_widgets.ForEach( {w => { if (w.SomeTestProperty)  w.SomeProperty = new_value});

Doing it in LINQ
You could use the normal query syntax and write

   var query = from w in bunch_of_widgets
                     where w.SomeTestProperty
                     select w.SomeProperty = new_value;

but that "var query" just hangs around being dead weight. Instead, using the method syntax we end up with

  bunch_of_widgets.Where(w => w.SomeTestProperty).Select(w=>w.SomeProperty = new_value);

The only problem now is that Select is deferred so we still have to use ToList() on the end. Actually there are a lot of immediate operators we could use Last(), ToArray(), Sum(w => w.someNumericField) etc. but ToList() seems to be the most common idiom. If you can do useful work with some other method then that should be used instead.

bunch_of_widgets.Where(w => w.SomeTestProperty).Select(w=>w.SomeProperty = new_value).ToList();

Now I'm not arguing that this is any better (or worse) than using for each loops. However, it does pay to start thinking of for each loops in terms of LINQ. One reason is because there is another form of the Select operator. Lets assume you have two Lists<> of  objects and you want to take a field from one object and assign it to the other object. A common approach is a ForEach loop and a temporary variable that uses an index like this

  int index = 0;
   foreach ( item in TargetList)
      item.SomeField = SourceList[index].SomeField

You could rewrite this in method syntax to reduce the vertical footprint

   int index = 0;
   TargetList.ForEach(t=> t.SomeField = SourceList[index++].SomeField);

However we still have that extra index and what's worse is we've added access to that variable in the lambda expression for what is known as access to a modified closure. To clean that up we can wrap the temp. variable in an array (now it's really starting to get ugly but hang in there)
   int[] index = {0};
   TargetList.ForEach(t => t.SomeField = SourceList[index[0]++].SomeField);

Luckily the LINQ Select operator will help out especially when we use the second form that allows the index as the second parameter :
   TargetList.Select((t,i)=>t.SomeField = SoureList[i]SomeField);

That's better, no temporary variable, no access to modified closures. The  only problem we have now is that the Select() operator is deferred so this statement never gets executed. So we add our idiomatic use of  ToList().

   TargetList.Select((t,i)=> t.SomeField = SourceList[i]SomeField).ToList();

Admittedly this example is pretty simple but even so it's more concise than the original foreach loop and you get even more benefits if you need to filter the source using a Where clause.

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.