10

In .NET generic interface ICollection<T> has Count property itself. But it does not inherit any non-generic interface with a Count property.

So, now if you want determine count of non-generic IEnumerable, you have to check whether it is implementing ICollection and if not, you have to use reflection to lookup whether it does implement generic ICollection<X>, since you do not know the generic argument X.

If ICollection<T> cannot inherit from directly from ICollection, why there is not another non-generic interface with Count property only?

Is it just bad design choice?

UPDATE: To make the question more clear, I demonstrate the issue on my current implementation:

    static int? FastCountOrZero(this IEnumerable items)
    {
        if (items == null)
            return 0;
        var collection = items as ICollection;
        if (collection != null)
            return collection.Count;
        var source = items as IQueryable;
        if (source != null)
            return QueryableEx.Count(source);
        // TODO process generic ICollection<> - I think it is not possible without using reflection
        return items.Cast<object>().Count();
    }
Community
  • 1
  • 1
TN.
  • 18,874
  • 30
  • 99
  • 157
  • IEnumerable might be infinite, so Count doesn't apply. – Wilbert Feb 26 '14 at 12:45
  • But not class that implements both `IEnumerable` and `ICollection`. – TN. Feb 26 '14 at 12:45
  • Ultimately, there just isn't an `ICountedEnumerable` interface. It would be nice if there were, but there isn't. – Jon Skeet Feb 26 '14 at 12:47
  • Also note that if you know something is an `IEnumerable`, if it implements `ICollection` at all it will *probably* implement `ICollection` - modulo covariance, of course. – Jon Skeet Feb 26 '14 at 12:48
  • 2
    Why don't you use `coll.Count()` and let .NET determine if it has a `Count` property or not? – Tim Schmelter Feb 26 '14 at 12:48
  • Yes, but that's how `Enumerable.Count()` is implemented. – TN. Feb 26 '14 at 12:49
  • `Count()` is just for generic version of `IEnumerable`. – TN. Feb 26 '14 at 12:49
  • 1
    @TN.: then you could still use `enumerable.Cast().Count()` since it still implements `ICollection` and therefore `Enumerable.Count` will use the `Count` property instead of enumerating all. – Tim Schmelter Feb 26 '14 at 12:54
  • @TimSchmelter Yes I can, but `Enumerable.Count()` could not use the count optimalization. – TN. Feb 26 '14 at 12:56
  • 1
    @TN.: it can if the object implements `ICollection`. For example: `IEnumerable enumerable = new List(); enumerable = enumerable.Cast(); int count = enumerable.Cast().Count()`. This will still use the `Count` property under the hood. – Tim Schmelter Feb 26 '14 at 12:57
  • @TN.: `Enumerable.Cast` will not return a `CastIterator` if the argument already implements `IEnumerable`. Then it just returns the original object. – Tim Schmelter Feb 26 '14 at 13:00
  • 1
    @TimSchmelter But this I can solve directly by casting to `ICollection`. It does not help with `ICollection`. Which is my question. (Since `ICollection` does not inherit non-generic `ICollection`.) – TN. Feb 26 '14 at 13:01
  • Both ICollection and ICollection have a Count getter that must be implemented, so I don;t understand your question. In addition, I agree with Wilbert regarding IEnumerable and Count not making sense on that interface. – Paul Marques Feb 26 '14 at 14:49
  • @PaulMarques Check the implementation of `System.Linq.Enumerable.Count()` in `System.Core.dll`. – TN. Feb 26 '14 at 14:55

2 Answers2

4

Is it just bad design choice?

Probably the answer is yes.

And to solve this issue in .NET 4.5 MS introduced IReadOnlyCollection<out T> interface, which is covariant for reference types.

So you can rewrite your code like following

static int? FastCountOrZero(this IEnumerable items)
{
    if (items == null)
        return 0;
    var collection = items as ICollection;
    if (collection != null)
        return collection.Count;
    var roCollection = items as IReadOnlyCollection<object>; // only for reference types
    if (roCollection != null)
        return roCollection.Count;
    var source = items as IQueryable;
    if (source != null)
        return QueryableEx.Count(source);

    return items.Cast<object>().Count();
}

And as last resort you can cast items to dynamic object and invoke Count property dynamicly.

if (items.GetType().GetInterface("System.Collections.Generic.ICollection`1") != null)
{
    dynamic dynamic = items;
    return dynamic.Count;
}
IS4
  • 11,945
  • 2
  • 47
  • 86
hazzik
  • 13,019
  • 9
  • 47
  • 86
-1

Best I can propose is this. Note (As mentioned in comments, Ienumerable can be infinite)

public static int CountIEnumerable (IEnumerable t)
{
    var iterator = t.GetEnumerator();
    var max = int.MaxValue;
    int count = 0;
    while (iterator.MoveNext() && (count < max))
    {
        count++;
    }

    //OPTIONAL
    //if (count >= max)
    //{
       //   throw new Exception("Collection is too big");
    //}
    return count;
    }

Edit : You can replace "int" by Int64 :)

Jurion
  • 1,230
  • 11
  • 18
  • This is the naive implementation. But it could be too slow if there are lot of items and if the `t` also implements `ICollection`. Btw. you also should check whether the `iterator` does not implement `IDisposable`, if so, you have to dispose the iterator in order to release resources. – TN. Feb 27 '14 at 07:20