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.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in C#, LINQ. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Brad Mead

    Thanks man. Concise, useful, practical. nice!

  • http://blog.brownie.com.au Andrew Browne

    Awesome post. Very useful and to the point.

  • http://devlicio.us Derik Whittaker

    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

  • http://Bryan.ReynoldsLive.com Bryan Reynolds

    Great post!

  • Jonathan

    Given the class in C#

    class MyObjct
    {
    public Description {get: set;}
    }

    How would one set all the Descriptions in a list.

    List Os = new List();
    Os.Add(new MyObject());
    Os.Add(new MyObject());

    currently I make a new list and add new objects
    List newOs = new List();
    foreach (MyObject o in Os)
    {
    o.Description = “Hello World”;
    newOs.Add(o);
    }

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @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

    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.

  • http://www.digitalactive.co.uk Simon

    Great article. Thanks.

  • Amit

    Nice 1

  • a

    a

  • http://www.progtalk.com Rajib Ahmed

    Jonathon,

    You could do the following:

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

  • Paul

    Excellent.  Four years after you published this article I found it useful. 

  • Neutrino .

    Your code doesn’t wrap and your article pane is fixed width, so none of the code is readable. Firefox 19.0.2.