Los Techies : Blogs about software and anything tech!

LINQ query operators: lose that foreach already!


Now that .NET 3.5 is out with all its LINQ query operator goodness, I feel like going on a mean streak of trashing a lot of our (now) pointless foreach loops.  Some example operations include:

  • Transformations
  • Aggregations
  • Concatenations
  • Filtering

As I mentioned in my last post, temporary list creation is a great pointer to find opportunities for losing the foreach statements.  I keep the foreach statement when the readability and understandability of the code drops with the LINQ change, but otherwise, a lot less temporary objects are floating around.  Personally, the jury is still out for me whether it's clearer to return "IEnumerable<LineItem>" over "LineItem[]", but the temporary array creation doesn't seem to have much of a point.

Transformations

Transformations are easy to spot. You'll create a new List<Something>, then loop through some other List<OtherThing> and create a Something from the OtherThing:

public OrderSummary[] FindOrdersFor(int customerId)
{
    IEnumerable<Order> orders = GetOrdersForCustomer(customerId);
    List<OrderSummary> orderSummaries = new List<OrderSummary>();

    foreach (Order order in orders)
    {
        orderSummaries.Add(new OrderSummary
                            {
                                CustomerName = order.Customer.Name,
                                DateSubmitted = order.DateSubmitted,
                                OrderTotal = order.GetTotal()
                            });
    }

    return orderSummaries.ToArray();
}

Note the temporary list creation, just to return an array.  With LINQ query operators, I can use the Select method to do the same transformation in less code:

public OrderSummary[] FindOrdersFor(int customerId)
{
    return GetOrdersForCustomer(customerId)
        .Select(order => new OrderSummary
                             {
                                 CustomerName = order.Customer.Name,
                                 DateSubmitted = order.DateSubmitted,
                                 OrderTotal = order.GetTotal()
                             })
        .ToArray();
}

By chaining the methods together, it comes out much more readable.

Aggregations

Aggregations can be found when you're looping through some list for some kind of calculation.  For example, the GetTotal method on Order loops to build up the total based on each item's ItemPrice:

public decimal GetTotal()
{
    decimal total = 0.0m;

    foreach (LineItem lineItem in GetLineItems())
    {
        total += lineItem.ItemPrice;
    }

    return total;
}

Again, this can be greatly reduced using LINQ query operators and the Sum method:

public decimal GetTotal()
{
    return GetLineItems()
        .Sum(item => item.ItemPrice);
}

Not only is the code much smaller, but the intent is much easier to discern.  Sometimes a calculation can be tricky, and in those cases LINQ isn't bringing anything to the table.  As always, use good judgement and keep an eye on readability.

Concatenations

Oftentimes I need to combine many lists into one flattened list.  For example, suppose I need a list of OrderLineItem summary items, perhaps to display on a grid to the end user.  However, I want to display all order line items for all orders, which is difficult to build up manually:

public LineItemSummary[] FindLineItemsFor(int customerId)
{
    IEnumerable<Order> orders = GetOrdersForCustomer(customerId);
    List<LineItemSummary> lineItemSummaries = new List<LineItemSummary>();

    foreach (Order order in orders)
    {
        IEnumerable<LineItemSummary> tempSummaries = TransformLineItems(order, order.GetLineItems());

        lineItemSummaries.AddRange(tempSummaries);
    }

    return lineItemSummaries.ToArray();
}

Note the two temporary lists: one to hold the concatenated list, and the other to hold each result as we loop through.  With the SelectMany method, this becomes much shorter:

public LineItemSummary[] FindLineItemsFor(int customerId)
{
    return GetOrdersForCustomer(customerId)
        .SelectMany(order => TransformLineItems(order, order.GetLineItems()))
        .ToArray();
}

No temporary lists are created, and all of the LineItemSummary objects are concatenated correctly.  Nested foreach loops as well as the AddRange method are indicators that the SelectMany method could be used.

Filtering

Filtering looks similar to transformations, except there's an "if" statement that controls adding to the temporary list.  Suppose we want only the expensive LineItemSummary items:

public LineItemSummary[] FindExpensiveLineItemsFor(int customerId)
{
    IEnumerable<Order> orders = GetOrdersForCustomer(customerId);
    List<LineItemSummary> lineItemSummaries = new List<LineItemSummary>();

    foreach (Order order in orders)
    {
        foreach (LineItem item in order.GetLineItems())
        {
            if (item.ItemPrice > 100.0m)
                lineItemSummaries.Add(new LineItemSummary
                                          {
                                              CustomerName = order.Customer.Name,
                                              DateSubmitted = order.DateSubmitted,
                                              ItemPrice = item.ItemPrice,
                                              ItemName = item.ProductName
                                          });
        }
    }

    return lineItemSummaries.ToArray();
}

This example has both concatenation and filtering.  The filtering can be taken care of with the Where method, and we'll use the same technique earlier with the SelectMany method:

public LineItemSummary[] FindExpensiveLineItemsFor(int customerId)
{
    return GetOrdersForCustomer(customerId)
        .SelectMany(order => TransformLineItems(order, order.GetLineItems()))
        .Where(item => item.ItemPrice > 100.0m)
        .ToArray();
}

By adding the Where method, we can filter out only the expensive line items.  The method chaining looks much better than the nested foreach loops coupled with the if statement, and got rid of the temporary list creation.

Lose the foreach

With the new LINQ query operators, any temporary list creation and foreach loop should be considered suspect.  By understanding the operations LINQ gives us, we can not only reduce the amount of code we create, but the end result matches the original intent far better.

Not every foreach or temporary list should be removed, as sometimes long chains and large lambdas tend to muddy rather than clear the picture.  But for a great deal of scenarios, LINQ query operators can vastly improve the readability of transformation (Select), aggregation (Sum), concatenation (SelectMany) and filtering (Where) sections of your code.

Kick It on DotNetKicks.com
Posted May 09 2008, 06:11 PM by bogardj
Filed under: ,

Comments

Weekly Links 3 « Davy Brion’s Blog wrote Weekly Links 3 &laquo; Davy Brion&#8217;s Blog
on 05-10-2008 6:05 AM

Pingback from  Weekly Links 3 &laquo; Davy Brion&#8217;s Blog

Dew Drop - May 10, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - May 10, 2008 | Alvin Ashcraft's Morning Dew
on 05-10-2008 9:22 AM

Pingback from  Dew Drop - May 10, 2008 | Alvin Ashcraft's Morning Dew

Brad Mead wrote re: LINQ query operators: lose that foreach already!
on 05-10-2008 1:41 PM

Thanks man. Concise, useful, practical. nice!

Andrew Browne wrote re: LINQ query operators: lose that foreach already!
on 05-11-2008 10:12 PM

Awesome post.  Very useful and to the point.

Derik Whittaker wrote re: LINQ query operators: lose that foreach already!
on 05-13-2008 2:29 PM

Just to point out.  The foreach is still being called, but it is NOT your code doing the foreach.  

This is great for 2 reasons.  It reduces your code and reduces the number of lines u need to test.

Nice post

Bryan Reynolds wrote re: LINQ query operators: lose that foreach already!
on 05-14-2008 12:25 AM

Great post!

Jonathan wrote re: LINQ query operators: lose that foreach already!
on 11-13-2008 1:52 PM

Given the class in C#

class MyObjct

{

    public Description {get: set;}

}

How would one set all the Descriptions in a list.

List<MyObject> Os = new List<MyObject>();

Os.Add(new MyObject());

Os.Add(new MyObject());

currently I make a new list and add new objects

List<MyObject> newOs = new List<MyObject>();

foreach (MyObject o in Os)

{

    o.Description = "Hello World";

    newOs.Add(o);

}

bogardj wrote re: LINQ query operators: lose that foreach already!
on 11-28-2008 5:37 PM

@Jonathan

The object initializer syntax will work for you.  But in your case, you're not working against an existing enumerable object, you're creating one from scratch.  Not much you can do in creating one from scratch, except for the collection initializer syntax.

Scott wrote re: LINQ query operators: lose that foreach already!
on 12-01-2008 4:14 PM

Hello All:

  Am new to LINQ queries and was told NOT to use LINQ to SQL queries (rats!).  I need to be able to add conditional LINQ statements depending on what the user has entered in text boxes and drop down boxes.  IE, if the text of a drop down box is not "ALL", I need to add a filter to limit the branch name of the branch dropdown boxes text value.  I need to do this for numerous other conditions.  Thanks for any help.

Simon wrote re: LINQ query operators: lose that foreach already!
on 03-04-2009 5:01 AM

Great article. Thanks.

LINQ and IEnumerable? | keyongtech wrote LINQ and IEnumerable? | keyongtech
on 05-28-2009 4:08 PM

Pingback from  LINQ and IEnumerable? | keyongtech

Amit wrote re: LINQ query operators: lose that foreach already!
on 09-02-2009 1:12 PM

Nice 1

a wrote re: LINQ query operators: lose that foreach already!
on 09-14-2009 6:02 AM

a

Rajib Ahmed wrote re: LINQ query operators: lose that foreach already!
on 09-20-2009 1:33 PM

Jonathon,

You could do the following:

newOs.ForEach( x => x.Description = "Hello World");

JetBrains .NET Tools Blog » Blog Archive » ReSharper 5.0 Preview: Loops 2 LINQ wrote JetBrains .NET Tools Blog &raquo; Blog Archive &raquo; ReSharper 5.0 Preview: Loops 2 LINQ
on 12-11-2009 8:49 AM

Pingback from  JetBrains .NET Tools Blog  » Blog Archive   » ReSharper 5.0 Preview: Loops 2 LINQ

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright Los Techies 2008, 2009. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems