and has 0 comments

Interfaces are cool. They abstract behavior that can be implemented in a lot of different ways. One of the most used interfaces, abstracting the enumerable software pattern so successfully that we have internalized it before we ever knew what a software pattern was, is IEnumerable. It's just a series of things that you can get (enumerate) one after the other until you reach an end.

However, there are a lot of situations where knowing the total number of enumerable items helps, like when you want to project it into another data structure and you would like to know how large it should be. Have you ever considered how ToArray works on an IEnumerable? Well, think about it now. If the IEnumerable cannot be cast to ICollection - which does export a Count - or something similar, then a buffer has to be allocated, data copied, the buffer increased as more data is coming, then copying the data from the buffer into an array, now that we know its final size. It's basically doing ToList().ToArray()!

Enter TryGetNonEnumeratedCount, introduced in .NET 6 for the first time as an extension method on IEnumerable<T>. It does exactly what is says, try to get the count without enumerating:

  1. if it's an ICollection<T>, it sets the count and returns true
  2. if it's an Iterator<T>, it sets the count only if it's cheap - more to follow, this was new to me, too - and returns true if count>0
  3. if it's an ICollection, it sets the count and returns true
  4. sets the count to 0 and returns false

I used to do this by hand! I've had this kind of extension almost in every project I worked on. This is very useful.

But what is that Iterator? Apparently, Iterator<TSource> is an internal abstract base class defined inside System.Linq.Enumerable in the .NET runtime (in System.Linq).

It is the base class for the specialized iterator types that LINQ creates for deferred (lazy) operators such as Where, Select, Take, Skip, etc. Concrete derived classes include things like WhereIterator, SelectIterator, and many others.

Having worked on a LiNQ package for Typescript/Javascript, I can tell you that you can optimize a lot of these primitives by holding some meta information ready.

Iterator<T> implements both IEnumerable<T> and IEnumerator<T>. The GetCount(bool onlyIfCheap) method is an abstract method there, implemented differently by the various internal iterators.

This was an interesting dive in the iterator mechanisms in .NET.

Comments

Be the first to post a comment

Post a comment