Beware of LINQ deferred execution

December 16th 2022 LINQ

Normally, you do not have to worry about deferred (or lazy) execution of LINQ operators. Everything works as expected, often even with better performance. However, you should be aware of it, because in some cases the code does not behave as expected because of it.

As an example of this, let us look at some code that uses the following method:

public IEnumerable<Wrapper<T>> CreateWrapper<T>(IEnumerable<T> items)
{
    return items.Select(item => new Wrapper<T>(item));
}

The method simply creates a new instance of a wrapper object for each item of the input sequence.

Let us use this method as follows:

var wrappers = CreateWrapper(items);

var store = new List<Wrapper<int>>();
store.AddRange(wrappers);

var storeContainsAnyWrappers = wrappers
    .Any(wrapper => store.Contains(wrapper)); // = false

We create the wrappers, insert them into a list, and then check if they are present in the list.

Without knowing how the CreateWrapper method is implemented, you would expect the wrapped items to be present in the list. But they are not. This is because the wrappers are created each time the wrappers sequence is enumerated.

You can easily check this by outputting text each time a new wrapper instance is created:

public IEnumerable<Wrapper<T>> CreateWrapper<T>(IEnumerable<T> items)
{
    return items.Select(item =>
    {
        Console.WriteLine($"Create wrapper for {item}");
        return new Wrapper<T>(item);
    });
}

If you do this, you will see that the above code creates all wrappers twice:

  • The first time when they are added to the store list.
  • The second time when checking if they are included in the store list.

If you want to avoid this, you need to materialize the result returned by CreateWrapper so that it is enumerated only once:

var wrappers = CreateWrapper(items).ToList();

var store = new List<Wrapper<int>>();
store.AddRange(wrappers);

var storeContainsAnyWrappers = wrappers
    .Any(wrapper => store.Contains(wrapper)); // = true

You can find the code that demonstrates the described behavior in my GitHub repository.

Note that when using LINQ, execution of the query is deferred until the sequence is enumerated. If you plan to enumerate the sequence multiple times, you should materialize the result beforehand to avoid re-evaluating the query with all its side effects.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License