views:

14645

answers:

13
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.

+5  A: 

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).

JesperE
The important point you brought up is that even when you get that IEnumerable object, you have to see if you can cast it to figure out what type it is. That's a very important point to those trying to use more IEnumerable like myself in my code.
CoffeeAddict
+28  A: 

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 IList, it has a Count property.

Mendelt
I'd favor ICollection over IList if you don't need to access the list by indexer.
Michael Meadows
I usually just grab List and IList out of habit. But especially if you want to implement them yourself ICollection is easier and also has the Count property. Thanks!
Mendelt
So how do I check the count when I have a given IEnumerable?
Shimmy
@Shimmy You iterate and count the elements. Or you call Count() from the Linq namespace that does this for you.
Mendelt
Should simply replacing IEnumerable with IList be sufficient under normal circumstances?
Helgi Hrafn Gunnarsson
@Helgi Because IEnumerable is evaluated lazily you can use it for things IList can't be used. You can build a function that returns an IEnumerable that enumerates all the decimals of Pi for example. As long as you never try to foreach over the complete result it should just work. You can't make an IList containing Pi.But that's all pretty academic. For most normal uses I completely agree. If you need Count you need IList. :-)
Mendelt
Makes sense, thanks. :) I converted my IEnumerables to IList and it was no problem at all!
Helgi Hrafn Gunnarsson
So what if you don't know what type the incoming list will be (dictionary, list, etc.). You want your method to be flexible and allow the caller to specify that type. But you still need to iterate that list or dictionary incoming? I guess then you'd have to check what type was passed inside your method to see if it is a whatever then do whatever
CoffeeAddict
what if you need a dictionary?
CoffeeAddict
@CoffeeAddict: There's nothing wrong with using an IEnumerable, I usually use this type in method signatures to be more flexible just like you said. But if you really need to get the number of elements in the collection without enumerating it then using IList (or even better ICollection as Muchael Meadows suggested) is the way to go. If I'm not mistaken IDictionary inherits from IList too as a list of key value pairs.
Mendelt
A: 

No.

Do you see that information available anywhere in the code you've written?

You might argue that the compiler can "see" that there are only two, but that would mean that it would need to analyze every iterator method looking just for that specific pathological case. And even if it did, how would you read it, given the limits of an IEnumerable?

James Curran
+1  A: 

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)
Joel Coehoorn
+4  A: 

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.

Chris Ammerman
that's a good point about not being able to count unless you iterate. The count functionality is tied to the classes that implement IEnumerable....thus you have to check what type IEnumerable is incoming (check by casting) and then you know that List<> and Dictionary<> have certain ways to count and then use those only after you know the type. I found this thread very useful personally so thanks for your reply as well Chris.
CoffeeAddict
+2  A: 

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

Robert Paulson
+1 for .Any being used to test for no elements
Josh Smeaton
+1  A: 

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

Jonathan Allen
A: 

Alternatively you can do the following:

Tables.ToList<string>.Count
A: 

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

JP Alioto
+14  A: 

The 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.

Daniel Earwicker
+1  A: 

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
      };
Samuel Jack
A: 

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)

Roman Golubin
A: 

Thanks Robert Paulson s relpy works