Search code examples
c#.netlinq.net-core

Equivalent of Linq's Any() function for when a collection has multiple items


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


Solution

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