Beware of LINQ deferred execution
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.