Combating Deferred Execution in LINQ with ToList(), ToArray() and ToDictionary()

As I’m sure you can tell by the title of this post that it is about deferred execution in LINQ. When a LINQ query (I know, it’s like saying, “NIC card” or “ATM machine”) is created the values are not immediatly determined. Only when the list is utilized are the results determined.

This bit me at first so hopefully with this it won’t bite you.

If you have:

List<Product> products = new List<Product>();
products.Add(new Product { Id = 1, Name = "Cheese", Quantity = 2 });
products.Add(new Product { Id = 2, Name = "Wine", Quantity = 2 });
products.Add(new Product { Id = 3, Name = "Crackers", Quantity = 0 });

var zeroInStock = from product in products
                  where product.Quantity == 0
                  select product;

foreach (var stock in zeroInStock)
{
    products.Remove(stock);
}

you will get:

InvalidOperationException was unhandled. Collection was modified; enumeration operation may not execute.

This is due to the deferred execution of LINQ. The results of zeroInStock are not calculated until you need them. The problem is that if you attempt to remove an item from the list in which the result originated from the original list is now not valid for the LINQ query. So how do you combat this? With ToList(), ToArray() and ToDictionary().

List<Product> products = new List<Product>();
products.Add(new Product { Id = 1, Name = "Cheese", Quantity = 2 });
products.Add(new Product { Id = 2, Name = "Wine", Quantity = 2 });
products.Add(new Product { Id = 3, Name = "Crackers", Quantity = 0 });

var zeroInStock = (from product in products
                  where product.Quantity == 0
                  select product).ToList();

foreach (var stock in zeroInStock)
{
    products.Remove(stock);
}

Now this code works. In this example ToList() and ToArray() could have been used interchangbly since we’re utilizing var but you may need one over the other. By calling ToList() or ToArray() it forces execution of the query. This means that now you can change the original list all you want without any issues.

ToDictionary() works a bit different. You have to specify a lambda expression to detemine what the key should be for each given result. Here’s the ToDictionary() example:

List<Product> products = new List<Product>();
products.Add(new Product { Id = 1, Name = "Cheese", Quantity = 2 });
products.Add(new Product { Id = 2, Name = "Wine", Quantity = 2 });
products.Add(new Product { Id = 3, Name = "Crackers", Quantity = 0 });

var zeroInStock = (from product in products
                  where product.Quantity == 0
                  select product.ToDictionary(x => x.Name);

foreach (var stock in zeroInStock)
{
    products.Remove(stock.Value);
}

You can see here the lambda expression means that the generated dictionary will be made of KeyValuePair<string, Product>. Since the product is now the value of the KeyValuePair the foreach that removes products with zero in stock now uses the Value.

If you are going from dictionary to dictionary as in:

Dictionary<int, Product> products = new Dictionary<int, Product>();
products.Add(1, new Product { Id = 1, Name = "Cheese", Quantity = 2 });
products.Add(2, new Product { Id = 2, Name = "Wine", Quantity = 2 });
products.Add(3, new Product { Id = 3, Name = "Crackers", Quantity = 0 });

var zeroInStock = (from product in products
                   where product.Value.Quantity == 0
                   select product).ToDictionary(x => x.Key);

foreach (var stock in zeroInStock)
{
    products.Remove(stock.Key);
}

you will most likely want to just use the existing key as I have above. Now, this doesn’t mean you can’t change the key but for my purposes I just used the existing key since I can use that to just remove from the dictionary.

That’s all for now. Leave me a comment if you have any questions.

Brian

Leave a Reply