Sam
Sam

Reputation: 42367

How can I efficiently determine if an IEnumerable has more than one element?

Given an initialised IEnumerable:

IEnumerable<T> enumerable;

I would like to determine if it has more than one element. I think the most obvious way to do this is:

enumerable.Count() > 1

However, I believe Count() enumerates the whole collection, which is unnecessary for this use case. For example, if the collection contains a very large amount of elements or provided its data from an external source, this could be quite wasteful in terms of performance.

How can I do this without enumerating any more than 2 elements?

Upvotes: 51

Views: 13615

Answers (4)

Matt Burland
Matt Burland

Reputation: 45135

I had a similar need, but to get the single value from an IEnumerable if it only has a single value. I made an extension method for it:

public static S OneOnlyOrDefault<S>(this IEnumerable<S> items)
{
    var rtn = default(S);
    using (var en = items.GetEnumerator())
    {
        if (en.MoveNext())
        {
            rtn = en.Current;
        }
        if (en.MoveNext())
        {
            rtn = default(S);
        }
    }
    return rtn;
}

To answer the question does this collection contain only 1 item? You could do (where the collection contains reference types in this case):

if (myList.OneOnlyOrDefault() == null)
{
    // list is either empty or contains more than one item
}

Upvotes: -1

hIpPy
hIpPy

Reputation: 5085

@Cameron-S's solution is simpler but below is more efficient. I came up with this based on Enumerable.Count() method. Skip() will always iterate and not short-circuit to get source's count for ICollection or ICollection<T> type.

/// <summary>
/// Returns true if source has at least <paramref name="count"/> elements efficiently.
/// </summary>
/// <remarks>Based on int Enumerable.Count() method.</remarks>
public static bool HasCountOfAtLeast<TSource>(this IEnumerable<TSource> source, int count)
{
    source.ThrowIfArgumentNull("source");
    var collection = source as ICollection<TSource>;
    if (collection != null)
    {
        return collection.Count >= count;
    }
    var collection2 = source as ICollection;
    if (collection2 != null)
    {
        return collection2.Count >= count;
    }
    int num = 0;
    checked
    {
        using (var enumerator = source.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                num++;
                if (num >= count)
                {
                    return true;
                }
            }
        }
    }
    // returns true for source with 0 elements and count 0
    return num == count;
}

Upvotes: 3

Andrevinsky
Andrevinsky

Reputation: 538

For the fun of it, call Next() twice, then get another IEnumerable.

Or, write a small wrapper class for this specific goal: EnumerablePrefetcher : IEnumerable<T> to try and fetch the specified amount of items upon initialization.

Its IEnumerable<T> GetItems() method should use yield return in this fashion

foreach (T item in prefetchedItems) // array of T, prefetched and decided if IEnumerable has at least n elements
{
  yield return item;
}
foreach (T item in otherItems) // IEnumerable<T>
{
  yield return item;
}

Upvotes: 5

Cameron S
Cameron S

Reputation: 2301

You can test this in many ways by combining the extension methods in System.Linq... Two simple examples are below:

bool twoOrMore = enumerable.Skip(1).Any();
bool twoOrMoreOther = enumerable.Take(2).Count() == 2;

I prefer the first one since a common way to check whether Count() >= 1 is with Any() and therefore I find it more readable.

Upvotes: 71

Related Questions