Question

I have the following scenario where I want to add some items to a List...

List<T> items = new List<T>();
IEnumerable<T> addItems = someCollection.Where(...);
items.AddRange(addItems);

Using this code, no items are added to the list but if I add a .ToList() after then Linq statement then the items are added correctly. I guess this is due to deferred execution but I would have thought that given the List.AddRange function accepts an IEnumerable that it would enumerate the items to be added.

Can someone please clear up why this happens?

Was it helpful?

Solution 3

Thanks for the replies. I tried to simplify the code for this example but as usual, the devil's in the details!

Between the .Where() statement and the AddRange() call the code was (deep down) clearing the source ('items' in this example) list. The developer didn't realise that the filter was deferred until the AddRange() call at which point they had already cleared the source list.

Glad to know I haven't lost the plot :)

OTHER TIPS

I guess this is due to deferred execution but I would have thought that given the List.AddRange function accepts an IEnumerable that it would enumerate the items to be added.

It does. There is a short circuit for ICollection<T> (which you wouldn't hit in this case), which would cause it to use ICollection<T>.CopyTo instead of enumerating the items, but otherwise, it will enumerate the collection.

For a working example, try:

using System;
using System.Linq;
using System.Collections.Generic;

internal class Program
{
    private static List<T> RunQuery<T>(IEnumerable<T> someCollection, Func<T, bool> predicate)
    {
        List<T> items = new List<T>();
        IEnumerable<T> addItems = someCollection.Where(predicate);
        items.AddRange(addItems);
        return items;
    }

    static void Main()
    {
        var values = Enumerable.Range(0, 1000);

        List<int> results = RunQuery(values, i => i >= 500);

        Console.WriteLine(results.Count);
        Console.WriteLine("Press key to exit:");
        Console.ReadKey();
    }
}

This uses your exact code, and will print out 500 (the proper number of items in the List<T>).

I would have thought that given the List.AddRange function accepts an IEnumerable that it would enumerate the items to be added.

I tried the below and AddRange(IEnumerable<T>) does work

List<string> someCollection = new List<string>{"A", "B", "C"};
List<string> items = new List<string>();
IEnumerable<string> addItems = someCollection.Where(x => x != "");
items.AddRange(addItems);

It does work. Here's a unit test that proves it:

[TestFixture]
public class AddRangeTest
{
    [Test]
    public void AddRange()
    {
        var list = new List<int>();
        var someCollection = new List<int> { 1, 2, 3 };
        var subItems = someCollection.Where(x => x > 1);
        list.AddRange(subItems);
        Assert.AreEqual(2, list.Count);
    }
}

Maybe there's something in your specific scenario that is not working correctly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top