2

I have the following lines of code:

var list = new List<Test>() { new Test("Test1"), new Test("Test2") };
var enumerable = list.Where(t => t.Content == "Test1");

Console.WriteLine($"Enumerable count: {enumerable.Count()}");
Console.WriteLine($"List count: {list.Count}");

list.RemoveAll(t => t.Content == "Test1");

Console.WriteLine($"Enumerable count: {enumerable.Count()}");
Console.WriteLine($"List count: {list.Count}");

I would expect the output to be

Enumerable count: 1
List count: 2
Enumerable count: 1
List count: 1

But in fact, the output is

Enumerable count: 1
List count: 2
Enumerable count: 0
List count: 1

Meaning removing the object from the list, also removes it from the IEnumerable. I thought I have a fairly firm grasp on object oriented programming, but this behaviour seems very unexpected to me.

Could anyone explain what's going on behind the scenes? I'll add that if I add .ToList() to the original Where-statement, it all works as I would have expected.

EDIT: The question was closed with a reference to a question about the difference between a list and IEnumerable. That is not at all relevant to what is going on in this question. The issue here was that the IEnumerable was a reference to the LINQ query itself, and not its own collection.

kling
  • 404
  • 1
  • 3
  • 11

1 Answers1

4

LINQ is lazy and the actual execution is deferred until the moment one of the materializable operations is invoked (Count, foreach, ToList, First, etc.). And the whole enumerable will be enumerated for every such operation. This is very easily observed with side-effect:

var enumerable = list
    .Where(t =>
    {
        Console.WriteLine("Test deffered: " + t.Content);
        return t.Content == "Test1";
    });

So in you case you will perform the enumeration twice processing the whole list (for every enumerable.Count()) but between the enumerations the list has changed so you see the effect.

This laziness is actually quite useful in many cases - for example when building queries (for the database via Entity Framework) in dynamic fashion:

var query = context.Something.AsQueriable();

if(filter.Name is not null)
{
   query = query.Where(s => s.Name == filter.Name);
}
...

Or reusing the query to fetch different results:

var query = ...;
var total = await query.CountAsync();

var page = await query.Skip(page*size).Take(size).ToListAsync();
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks for the clear reply. I interpreted the IEnumerable as its own collection, when in fact it's just a reference to the original LINQ query. – kling Feb 09 '23 at 14:44
  • 1
    @kling it can be both. Basically it is just an interface which represents something which can be enumerated (so sometimes you should be cautions with it to prevent double enumerations) – Guru Stron Feb 09 '23 at 14:46
  • Question has been closed with a reference to the difference between IEnumerable and List which completely misses the point... – kling Feb 09 '23 at 14:50
  • 1
    @kling If you disagree with the close reason, you can leave a comment on your question and use the @ syntax to direct your comment towards the person who closed your question. You'd want to explain why you think that your question isn't a duplicate of that one. – mason Feb 09 '23 at 14:56