3
\$\begingroup\$

Let's say I have a collection of expensive-to-generate objects stored in an IEnumerable:

IEnumerable<Expensive> expensiveObjects = CreateExpensiveIEnumerable();

Now I will iterate over everything in expensiveObjects and afterwards, I want to know if it was empty.

Here is how I am doing that currently.

var count = 0;
foreach(var e in expensiveObjects)
{
    count++;
    //other processing
}
if (count == 0)
{
    //do something
}

This works fine, but I'm asking here as a fishing expedition to figure out if there are any clever ways to implement this functionality without either:

  • iterating over at least part of the collection twice by using the Any() method
  • Performing a ToList() up-front on my collection

Any other suggestions or should I just stick with a simple counting variable?

\$\endgroup\$
  • \$\begingroup\$ I'm confused, you say the objects are “stored” in the IEnumerable but you also don't want to iterate it twice, which looks like the objects are generated each time. So which one is it? Could you describe the characteristics on the IEnumerable in more detail? \$\endgroup\$ – svick Nov 15 '13 at 18:26
  • \$\begingroup\$ Sorry for any confusion, let's say CreateExpensiveIEnumerable() generates its return values through a yield return statement, does that help? \$\endgroup\$ – Sven Grosen Nov 15 '13 at 18:30
5
\$\begingroup\$

I think your approach is reasonable if you don't want (or can't) have the whole collection in memory. One minor change: if you don't need the count, just whether the collection was empty or not, use a simple bool:

bool any = false;

foreach(var e in expensiveObjects)
{
    any = true;

    //other processing
}

if (any)
{
    //do something
}
\$\endgroup\$
2
\$\begingroup\$

One alternative would be to enclose this logic into an extension method:

static bool ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    bool any = false;

    foreach (var x in source)
    {
        any = true;

        action(x);
    }

    return any;
}

Usage:

bool any = expensiveObjects.ForEach(e => /* other processing */);

if (any)
{
    //do something
}

But I think that in this specific case, this doesn't make much sense and using normal foreach with additional variable is the better solution.

\$\endgroup\$
1
\$\begingroup\$

It depends on the type that's implementing your IEnumerable. Per this Jon Skeet SO answer, calling .ToList() doesn't automatically mean an enumeration.

I'd make sure expensiveObjects is some IList, and if that's the case I'd call .ToList() up-front, using the Count property instead of incrementing a count variable during the enumeration.

var items = expensiveObjects as IList<Expensive> ?? expensiveObjects.ToList();

Now if expensiveObjects is some "pure" IEnumerable, I believe this would effectively iterate the items, so it could be more efficient to increment a count variable as you're iterating.

The best way to find out, ...is to try & profile both ways :)

\$\endgroup\$
  • \$\begingroup\$ In my example, this is a "pure" IEnumerable, so based on your answer, I'd be best off with my current approach. \$\endgroup\$ – Sven Grosen Nov 15 '13 at 19:15
  • \$\begingroup\$ I would think so, but profiling/timing both alternatives (or being more knowledgeable about these subtleties than I am :s) would be the best way to find out for sure. \$\endgroup\$ – Mathieu Guindon Nov 15 '13 at 19:29
  • \$\begingroup\$ @ledbutter Why not generate them into a List<T>? \$\endgroup\$ – Mathieu Guindon Nov 15 '13 at 21:43
  • 1
    \$\begingroup\$ because then they would all be in memory at once, and I'd rather not do that \$\endgroup\$ – Sven Grosen Nov 15 '13 at 22:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.