2

In C#, using Linq, if I want to check if a collection has any elements, I can do this:

someCollection.Any()

which is preferable over

someCollection.Count() > 0

since the latter will count all the items in the collection, when I don't really care how many items there are, I just want to know if there are any.

Is there an equivalent for checking if a collection has more than one item? Something like:

someCollection.Many()

instead of having to do

someCollection.Count() > 1

Silly question, I know, and from my research, it doesn't look like there is. But since it's something I use frequently, I thought I'd double check with the community.

Thanks

Fabricio Rodriguez
  • 3,769
  • 11
  • 48
  • 101
  • Yes, .Count() > 1 is the way to do that. – Cetin Basoz Aug 22 '22 at 09:42
  • 4
    I'd use `source.Take(2).Count() == 2` - that way it won't iterate over all of `source`. – Dai Aug 22 '22 at 09:42
  • 1
    @CetinBasoz `.Count()` will iterate over _all_ of the source `IEnumerable`, which isn't what the OP wants. – Dai Aug 22 '22 at 09:43
  • 5
    You can also do source.Skip(1).Any() – Rask Aug 22 '22 at 09:44
  • @Dai, but other ways suggested, are they really worth enough to use? I didn't benchmark any of them, but at least, a solution using Skip would be worse I think. Anyway, we don't even know what that someCollection is, depending on what that is checking the item at position 2 might be a simple and efficient way. If an array, length would be an answer already. – Cetin Basoz Aug 22 '22 at 11:01
  • @CetinBasoz That's why my answer checks for `IReadOnlyCollection.Count` and `ICollection.Count` first. – Dai Aug 22 '22 at 11:09

1 Answers1

4

As per my comment:

First, you should check to see if IEnumerable<T> source actually is an IReadOnlyCollection<T> or ICollection<T> because that has a .Count property you can use - which would be preferable to any iteration.

Assuming your IEnumerable<T> does not have an O(1) .Count property, if you want to see if there's at least 1 element (i.e. "at least 2 or more") then use source.Take(2).Count() == 2 or source.Skip(1).Any().

Like so:

public static Boolean Many<T>( this IEnumerable<T> source )
{
    if( source is null ) throw new ArgumentNullException(nameof(source));

    if( source is ICollection<T> col ) return col.Count >= 2;
    else if( source is IReadOnlyCollection<T> roCol ) return roCol.Count >= 2;

    return source.Take(2).Count() == 2;
}

If you want to be more efficient about it, do manual iteration:

public static Boolean Many<T>( this IEnumerable<T> source )
{
    if( source is null ) throw new ArgumentNullException(nameof(source));

    if( source is ICollection<T> col ) return col.Count >= 2;
    else if( source is IReadOnlyCollection<T> roCol ) return roCol.Count >= 2;

    Int32 count = 0;
    using( IEnumerator<T> iter = source.GetEnumerator() )
    {
        while( iter.MoveNext() && count < 2 )
        {
            count += 1;
        }
    }

    return count == 2;
}

If you want to be even more efficient about it, allow consumers to supply non-boxed enumerators (e.g. List<T>.Enumerator):

public static Boolean Many<TEnumerable,TEnumerator,TElement>( /*this*/ TEnumerable source, Func<TEnumerable,TEnumerator> getEnumerator )
    where TEnumerable : IEnumerable<TElement>
    where TEnumerator : IEnumerator<TElement>
{
    if( source        is null ) throw new ArgumentNullException(nameof(source));
    if( getEnumerator is null ) throw new ArgumentNullException(nameof(getEnumerator));
    
    //

    if     ( source is ICollection<TElement>           col ) return col  .Count >= 2;
    else if( source is IReadOnlyCollection<TElement> roCol ) return roCol.Count >= 2;

    Int32 count = 0;
    using( TEnumerator iter = getEnumerator( source ) )
    {
        while( iter.MoveNext() && count < 2 )
        {
            count += 1;
        }
    }

    return count == 2;
}

Used like so:

List<String> listOfStrings = new List<String>() { ... };

if( listOfStrings.Many</*TEnumerable:*/ List<String>, /*TEnumerator:*/ List<String>.Enumerator, /*TElement:*/ String >( l => l.GetEnumerator() ) )
{
    
}
  • Yes, it's ugly... it's unfortunate, but C# still doesn't support this level of generic type-inference - nor does it support partial generic parameter application.
  • The list.GetEnumerator() part is needed as it's the only way to pass a struct-based enumerator into generic code without boxing (at least, not without reflection).
Dai
  • 141,631
  • 28
  • 261
  • 374
  • 1
    Take a look at the [`TryGetNonEnumeratedCount`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.trygetnonenumeratedcount) LINQ operator ([source code](https://github.com/dotnet/runtime/blob/3c21cc5a0a486d08b5a7a4fdc2e1c2f9b02dbc9f/src/libraries/System.Linq/src/System/Linq/Count.cs#L95)). – Theodor Zoulias Aug 22 '22 at 10:17
  • 1
    @TheodorZoulias Thanks - I've updated my post. I verified the code works in Linqpad 7. – Dai Aug 22 '22 at 13:15
  • Yep, now it's OK. I noticed that it compiles also without the `where TEnumerable : IEnumerable` constraint. – Theodor Zoulias Aug 22 '22 at 13:38