336
votes
private IEnumerable<string> Tables
{
    get
    {
        yield return "Foo";
        yield return "Bar";
    }
}

Let's say I want iterate on those and write something like processing #n of #m.

Is there a way I can find out the value of m without iterating before my main iteration?

I hope I made myself clear.

19

19 Answers

359
votes

IEnumerable doesn't support this. This is by design. IEnumerable uses lazy evaluation to get the elements you ask for just before you need them.

If you want to know the number of items without iterating over them you can use ICollection<T>, it has a Count property.

236
votes

The System.Linq.Enumerable.Count extension method on IEnumerable<T> has the following implementation:

ICollection<T> c = source as ICollection<TSource>;
if (c != null)
    return c.Count;

int result = 0;
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
    while (enumerator.MoveNext())
        result++;
}
return result;

So it tries to cast to ICollection<T>, which has a Count property, and uses that if possible. Otherwise it iterates.

So your best bet is to use the Count() extension method on your IEnumerable<T> object, as you will get the best performance possible that way.

95
votes

Just adding extra some info:

The Count() extension doesn't always iterate. Consider Linq to Sql, where the count goes to the database, but instead of bringing back all the rows, it issues the Sql Count() command and returns that result instead.

Additionally, the compiler (or runtime) is smart enough that it will call the objects Count() method if it has one. So it's not as other responders say, being completely ignorant and always iterating in order to count elements.

In many cases where the programmer is just checking if( enumerable.Count != 0 ) using the Any() extension method, as in if( enumerable.Any() ) is far more efficient with linq's lazy evaluation as it can short-circuit once it can determine there are any elements. It's also more readable

12
votes

A friend of mine has a series of blog posts that provide an illustration for why you can't do this. He creates function that return an IEnumerable where each iteration returns the next prime number, all the way to ulong.MaxValue, and the next item isn't calculated until you ask for it. Quick, pop question: how many items are returned?

Here are the posts, but they're kind of long:

  1. Beyond Loops (provides an initial EnumerableUtility class used in the other posts)
  2. Applications of Iterate (Initial implementation)
  3. Crazy Extention Methods: ToLazyList (Performance optimizations)
10
votes

IEnumerable cannot count without iterating.

Under "normal" circumstances, it would be possible for classes implementing IEnumerable or IEnumerable<T>, such as List<T>, to implement the Count method by returning the List<T>.Count property. However, the Count method is not actually a method defined on the IEnumerable<T> or IEnumerable interface. (The only one that is, in fact, is GetEnumerator.) And this means that a class-specific implementation cannot be provided for it.

Rather, Count it is an extension method, defined on the static class Enumerable. This means it can be called on any instance of an IEnumerable<T> derived class, regardless of that class's implementation. But it also means it is implemented in a single place, external to any of those classes. Which of course means that it must be implemented in a way that is completely independent of these class' internals. The only such way to do counting is via iteration.

10
votes

Alternatively you can do the following:

Tables.ToList<string>().Count;
8
votes

No, not in general. One point in using enumerables is that the actual set of objects in the enumeration is not known (in advance, or even at all).

8
votes

You can use System.Linq.

using System;
using System.Collections.Generic;
using System.Linq;

public class Test
{
    private IEnumerable<string> Tables
    {
        get {
             yield return "Foo";
             yield return "Bar";
         }
    }

    static void Main()
    {
        var x = new Test();
        Console.WriteLine(x.Tables.Count());
    }
}

You'll get the result '2'.

5
votes

Going beyond your immediate question (which has been thoroughly answered in the negative), if you're looking to report progress whilst processing an enumerable, you might want to look at my blog post Reporting Progress During Linq Queries.

It lets you do this:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += (sender, e) =>
      {
          // pretend we have a collection of 
          // items to process
          var items = 1.To(1000);
          items
              .WithProgressReporting(progress => worker.ReportProgress(progress))
              .ForEach(item => Thread.Sleep(10)); // simulate some real work
      };
5
votes

I used such way inside a method to check the passed in IEnumberable content

if( iEnum.Cast<Object>().Count() > 0) 
{

}

Inside a method like this:

GetDataTable(IEnumberable iEnum)
{  
    if (iEnum != null && iEnum.Cast<Object>().Count() > 0) //--- proceed further

}
4
votes

It depends on which version of .Net and implementation of your IEnumerable object. Microsoft has fixed the IEnumerable.Count method to check for the implementation, and uses the ICollection.Count or ICollection< TSource >.Count, see details here https://connect.microsoft.com/VisualStudio/feedback/details/454130

And below is the MSIL from Ildasm for System.Core, in which the System.Linq resides.

.method public hidebysig static int32  Count<TSource>(class 

[mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source) cil managed
{
  .custom instance void System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       85 (0x55)
  .maxstack  2
  .locals init (class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource> V_0,
           class [mscorlib]System.Collections.ICollection V_1,
           int32 V_2,
           class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> V_3)
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_000e
  IL_0003:  ldstr      "source"
  IL_0008:  call       class [mscorlib]System.Exception System.Linq.Error::ArgumentNull(string)
  IL_000d:  throw
  IL_000e:  ldarg.0
  IL_000f:  isinst     class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>
  IL_0014:  stloc.0
  IL_0015:  ldloc.0
  IL_0016:  brfalse.s  IL_001f
  IL_0018:  ldloc.0
  IL_0019:  callvirt   instance int32 class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>::get_Count()
  IL_001e:  ret
  IL_001f:  ldarg.0
  IL_0020:  isinst     [mscorlib]System.Collections.ICollection
  IL_0025:  stloc.1
  IL_0026:  ldloc.1
  IL_0027:  brfalse.s  IL_0030
  IL_0029:  ldloc.1
  IL_002a:  callvirt   instance int32 [mscorlib]System.Collections.ICollection::get_Count()
  IL_002f:  ret
  IL_0030:  ldc.i4.0
  IL_0031:  stloc.2
  IL_0032:  ldarg.0
  IL_0033:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::GetEnumerator()
  IL_0038:  stloc.3
  .try
  {
    IL_0039:  br.s       IL_003f
    IL_003b:  ldloc.2
    IL_003c:  ldc.i4.1
    IL_003d:  add.ovf
    IL_003e:  stloc.2
    IL_003f:  ldloc.3
    IL_0040:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    IL_0045:  brtrue.s   IL_003b
    IL_0047:  leave.s    IL_0053
  }  // end .try
  finally
  {
    IL_0049:  ldloc.3
    IL_004a:  brfalse.s  IL_0052
    IL_004c:  ldloc.3
    IL_004d:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0052:  endfinally
  }  // end handler
  IL_0053:  ldloc.2
  IL_0054:  ret
} // end of method Enumerable::Count
4
votes

I think the easiest way to do this

Enumerable.Count<TSource>(IEnumerable<TSource> source)

Reference: system.linq.enumerable

2
votes

Here is a great discussion about lazy evaluation and deferred execution. Basically you have to materialize the list to get that value.

2
votes

Result of the IEnumerable.Count() function may be wrong. This is a very simple sample to test:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;

namespace Test
{
  class Program
  {
    static void Main(string[] args)
    {
      var test = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
      var result = test.Split(7);
      int cnt = 0;

      foreach (IEnumerable<int> chunk in result)
      {
        cnt = chunk.Count();
        Console.WriteLine(cnt);
      }
      cnt = result.Count();
      Console.WriteLine(cnt);
      Console.ReadLine();
    }
  }

  static class LinqExt
  {
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkLength)
    {
      if (chunkLength <= 0)
        throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0");

      IEnumerable<T> result = null;
      using (IEnumerator<T> enumerator = source.GetEnumerator())
      {
        while (enumerator.MoveNext())
        {
          result = GetChunk(enumerator, chunkLength);
          yield return result;
        }
      }
    }

    static IEnumerable<T> GetChunk<T>(IEnumerator<T> source, int chunkLength)
    {
      int x = chunkLength;
      do
        yield return source.Current;
      while (--x > 0 && source.MoveNext());
    }
  }
}

Result must be (7,7,3,3) but actual result is (7,7,3,17)

2
votes

The best way I found is count by converting it to a list.

IEnumerable<T> enumList = ReturnFromSomeFunction();

int count = new List<T>(enumList).Count;
-1
votes

I would suggest calling ToList. Yes you are doing the enumeration early, but you still have access to your list of items.

-1
votes

It may not yield the best performance, but you can use LINQ to count the elements in an IEnumerable:

public int GetEnumerableCount(IEnumerable Enumerable)
{
    return (from object Item in Enumerable
            select Item).Count();
}
-2
votes

I use IEnum<string>.ToArray<string>().Length and it works fine.

-6
votes

I use such code, if I have list of strings:

((IList<string>)Table).Count