5

Given an instance IEnumerable o how can I get the item Count? (without enumerating through all the items)

For example, if the instance is of ICollection, ICollection<T> and IReadOnlyCollection<T>, each of these interfaces have their own Count method.

Is getting the Count property by reflection the only way?

Instead, can I check and cast o to ICollection<T> for example, so I can then call Count ?

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Don Box
  • 3,166
  • 3
  • 26
  • 55
  • 5
    *Linq* `Count()`? – Dmitry Bychenko Sep 24 '18 at 09:31
  • AFAIK, under the hood, it's actually enumerating through all items. It doesn't do what I'd like – Don Box Sep 24 '18 at 09:32
  • 2
    Yeah, but how else do you think a count is performed, if not by iterating the collection? Btw.: a `Count()` on a `List` for example is mapped to its property. So the result is cached in case of an `ICollection`. See https://stackoverflow.com/questions/7969354/count-property-vs-count-method – MakePeaceGreatAgain Sep 24 '18 at 09:32
  • 1
    @DonBox It firsts tries to cast it to a List type https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,41ef9e39e54d0d0b – Pretasoc Sep 24 '18 at 09:33
  • 4
    `Count()` is *smart enough* to call `Count` for some types https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,41ef9e39e54d0d0b – Dmitry Bychenko Sep 24 '18 at 09:33
  • 1
    Counting the items without enumerating them first is only possible, if the collection type actually has information about the count. If the IEnumerable is a result of an iterator function or a Linq statement that could change the number of elements (e.g. SelectMany) there is no way to know the item count before enumerating. – Mario Dekena Sep 24 '18 at 09:38
  • if IEnumerable is based on a yield (holds the state for a single item at a time) you are out of luck – TheGeneral Sep 24 '18 at 09:38
  • @DmitryBychenko That is the `Count()` extension method. I needed the non-generic `Count()` But as long as you know a way to call the generic one, it's fine with me. I am editing my question now to be clear what I want – Don Box Sep 24 '18 at 09:39
  • 1
    @HimBromBeere - it is *mostly* a duplicate of https://stackoverflow.com/questions/7969354/count-property-vs-count-method. But, oddly enough, [`Enumerable.Count()`](https://github.com/Microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L1314) doesn't check for `IReadOnlyCollection`. So maybe there's something to be said here. – dbc Sep 24 '18 at 09:39
  • 1
    Why are people voting to close this question as off topic of all the things? – Stilgar Sep 24 '18 at 09:45

3 Answers3

3

IEnumerable doesn't promise a count . What if it was a random sequence or a real time data feed from a sensor? It is entirely possible for the collection to be infinitely sized. The only way to count them is to start at zero and increment for each element that the enumerator provides. Which is exactly what LINQ does, so don't reinvent the wheel. LINQ is smart enough to use .Count properties of collections that support this.

CarenRose
  • 1,266
  • 1
  • 12
  • 24
Adam G
  • 1,283
  • 1
  • 6
  • 15
  • 1
    Just to note if you really care if your particular IEnumerable has optimized Count you should look at the code. – Stilgar Sep 24 '18 at 09:46
  • LINQ's method Count is extension for an IEnumerable not for an IEnumerable. I'd have to know if that IEnumerable is actually an IEnumerable then cast so I could then use LINQ's IEnumerable.Count() – Don Box Sep 24 '18 at 10:54
  • @DonBox Why in the world do you have a non-generic IEnumerable in 2018? If you control the code please switch to the generic version. That will not only give you access to LINQ methods but will also improve the general performance. If you don't know the types of the objects (which is strange but can happen) just use IEnumerable – Stilgar Sep 24 '18 at 12:56
  • You can find the LINQ source here: https://github.com/Microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs . If you really want an Ienumerable (non generic), then drop the bits in your own copy of that method. But as others have mentioned, the non generic version was superceded in .Net 2.0. It's main use today is backwards compatibility with earlier code. – Adam G Sep 24 '18 at 14:05
3

It depends how badly you want to avoid enumerating the items if the count is not available otherwise.

If you can enumerate the items, you can use the LINQ method Enumerable.Count. It will look for a quick way to get the item count by casting into one of the interfaces. If it can't, it will enumerate.

If you want to avoid enumeration at any cost, you will have to perform a type cast. In a real life scenario you often will not have to consider all the interfaces you have named, since you usually use one of them (IReadOnlyCollection is rare and ICollection only used in legacy code). If you have to consider all of the interfaces, try them all in a separate method, which can be an extension:

static class CountExtensions {
    public static int? TryCount<T>(this IEnumerable<T> items) {
        switch (items) {
            case ICollection<T> genCollection:
                return genCollection.Count;
            case ICollection legacyCollection:
                return legacyCollection.Count;
            case IReadOnlyCollection<T> roCollection:
                return roCollection.Count;
            default:
                return null;
        }
    }
}

Access the extension method with:

int? count = myEnumerable.TryCount();
Sefe
  • 13,731
  • 5
  • 42
  • 55
  • Why returning `null` in case of a generic `IEnumerable`? Just use the extension-method in the `default`-case. – MakePeaceGreatAgain Sep 24 '18 at 10:05
  • @HimBromBeere: The method is specifically designed to *not* enumerate the enumerable (as `Enumerable.Count` would do it). The answer provides two options: `Enumerable.Count` or the custom extension. – Sefe Sep 24 '18 at 10:08
  • LINQ's method Count is for IEnumerable not for IEnumerable – Don Box Sep 24 '18 at 10:53
  • @DonBox: What is the reason to use `IEnumerable` and not `IEnumerable`? – Sefe Sep 24 '18 at 12:54
1

The only way to really cover all your possible types for a collection is to use the generic interface and call the Count-method. This also covers other types such as streams or just iterators. Furthermore it will use the Count-property as of Count property vs Count() method? to avoid unneccessary overhead.

If you however have a non-generic collection you´d have to use reflection to use the correct property. However this is cumbersome and may fail if your collection doesn´t even have the property (e.g. an endless stream or just an iterator). On the other hand IEnumerable<T>.Count() will handle those types with the optimization mentioned above. Only if neccessary it will iterate the entire collection.

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111