0

Given a list of object with a property of type List:

class Bar{
   public List<Foo> Foos{get;set;}
}

Will the following code, that select all bar with more than one Foo, count all Foos?
Or will it stop iterating at 2 Foos?

var input = new List<Bar>();
var result = input.Where(x=> x.Foos.Count()>1).ToList();
Drag and Drop
  • 2,672
  • 3
  • 25
  • 37
  • The code, as shown, will not count any Foos since Where is lazily evaluated. – Hans Kilian Sep 10 '20 at 11:41
  • 1
    Related: https://stackoverflow.com/questions/7969354/count-property-vs-count-method – crashmstr Sep 10 '20 at 11:41
  • 4
    An alternative, to prevent iterating an entire enumerable (i.e. when it's not `ICollection`) is to use `input.Skip(1).Any()`. – Enigmativity Sep 10 '20 at 11:54
  • When using LinQ Count(), you expect something to be enumerate. Limiting this Count in a Where clause "Is there more than one item?" is normal to expect only to count to 1. That's a contradiction with the count tho the question. – Drag and Drop Sep 10 '20 at 11:56
  • @MartinBackasch: Well x => **c**.Foos... won't even compile, so I assumed `Count()` (instead of `Count`) to be a typo as well. – Heinzi Sep 10 '20 at 11:58
  • The c was a typo. It's x. I typed in SO directly. My bad. Sorry. – Drag and Drop Sep 10 '20 at 12:00
  • @Heinzi: ah sorry, I missed that x => c change. I only looked at the title and the `Count` within the body. – Martin Backasch Sep 10 '20 at 12:05
  • 1
    @Enigmativity: I have taken the liberty of adding your comment to my answer (it's too useful to be lost in a comment). – Heinzi Sep 10 '20 at 12:05
  • @HansKilian, Yes, it's lazy, but `input` is an empty list so `x.Foos` is null. it will at least throw a null exception. – Drag and Drop Sep 10 '20 at 12:13

2 Answers2

5

It won't count anything. List<T> redundantly stores the number of elements, so accessing the Count property is a O(1) operation.

This works even if you use the Enumerable.Count() extension method rather than List<T>s built-in Count property, because Enumerable.Count() has a built-in optimization if the underlying data source implements ICollection<T>.


As mentioned by Enigmativity in the comments: If you have an IEnumerable which is not an ICollection<T>, you can use the following instead to prevent iterating the entire enumerable:

var result = input.Where(x => x.Foos.Skip(1).Any()).ToList();
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • I expected the : "No,It won't count". But I expected it to be because in some kind of expression tree, this yield to that , and it's stop enumerating. Not count doesn't count on ICollection. – Drag and Drop Sep 10 '20 at 12:07
  • 1
    @DragandDrop: As far as I know LINQ-to-objects (as opposed to LINQ-to-SQL or EF) does not use expression tree evaluation. Thus, `Count()` will either be executed or it won't, and if it is executed, it won't know the context (`... > 1`) in which it is used. – Heinzi Sep 10 '20 at 12:29
  • For everything inside the `System.Collections`, that's almost everything the light touch, it won't because of the [optimisation](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,1314). I was perhaps to simple, I "_Expected_" count to count and linQ to be trees that get evaluated. But C# dev are smarter than me, here count is faster than skip().any, Because it's a simple type check access a property versus a getEnumerator etc. So even if it knew the context it doesn't need to. – Drag and Drop Sep 10 '20 at 12:45
  • 1
    When I said I was not expecting, I did not disapproved in any way the answer. I had a simple Where Count, and asked myself perhaps it count to much. And it does not. I fantasize about how it solve a problem it didn't have. – Drag and Drop Sep 10 '20 at 12:56
3

When you have questions about how parts of .Net works it's ideal to look at the source code

this is source for List.Count

    // Read-only property describing how many elements are in the List.
    public int Count {
        get {
            Contract.Ensures(Contract.Result<int>() >= 0);
            return _size; 
        }
    }

_size is changed whenever the underlying collection is changed, so it doesn't actually count it just references the known size of the list.

Jpsh
  • 1,697
  • 12
  • 17