Using LINQ with different collection types 

We all work with different types of collections in C# and I wanted to unwrap a pitfall with some ways of determining if a collection contains any items. 

Lets consider the following code statements

var myList = new List<Guid>(Enumerable.Range(0, 10000).Select(e => Guid.NewGuid()));

//List property
bool result = myList.Count > 0;

//Linq .Any()
bool linqResult = myList.Any();

We instantiate a list of Guids and then try to figure out if there is any item in the list using two different methods. Easy enough, so why do I bring this up? I bring this up because I recently learned something interesting about these two different approaches that I think is worth knowing. And importantly so because this is the kind of code that we come across very frequently.

Lets run some benchmarks on the behavior of the two methods using BenchMarkDotNet nuget package.

class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<Bench>();
    }
}

public class Foo
{
    public List<Guid> List { get; } =
           new List<Guid>(Enumerable.Range(0, 10000).Select(e => Guid.NewGuid()));
}

public class Bench
{
     private static readonly Foo Foo = new Foo();

     [Benchmark]
     public bool TestAny() => Foo.List.Any();

     [Benchmark]
     public bool TestCount() => Foo.List.Count > 0;

}

Results:

|    Method |       Mean |     Error |    StdDev |     Median |
|---------- |-----------:|----------:|----------:|-----------:| 
|   TestAny | 17.1321 ns | 0.3162 ns | 0.2640 ns | 17.0281 ns |
| TestCount |  0.0366 ns | 0.0267 ns | 0.0391 ns |  0.0271 ns | 

As you can see, using the 'count' property of the list is many times faster then the Linq .Any() method, all be it, still in the nano seconds range.

 So, why is that? Lets take a look at the implementation of .Any(). Source.

  public static bool Any<TSource>(this IEnumerable<TSource> source) {
            if (source == null) throw Error.ArgumentNull("source");
            using (IEnumerator<TSource> e = source.GetEnumerator()) {
                if (e.MoveNext()) return true;
            }
            return false;
        }

Here we are calling .GetEnumerator() and the .MoveNext() methods to be able to understand if there are items in the collection. Whilst the Count property on the 'List' is already computed and we simply get the value returned to us.

This is not all that complicated, and as you probably know we have an '.Count()' method on the IEnumerable<T> interface. We do not have the 'Count' property since the IEnumerable can be any collection, or more specifically any object that you can enumerate. 

So if you call '.Count()' on an IEnumarble<T> where the implementation is a collection with the 'Count' property, it will be returned and only defaulting to similar logic above if necessary.

Here is implementation of the '.Count()' method. Source.

public static int Count<TSource>(this IEnumerable<TSource> source) {
            if (source == null) throw Error.ArgumentNull("source");
            ICollection<TSource> collectionoft = source as ICollection<TSource>;
            if (collectionoft != null) return collectionoft.Count;
            ICollection collection = source as ICollection;
            if (collection != null) return collection.Count;
            int count = 0;
            using (IEnumerator<TSource> e = source.GetEnumerator()) {
                checked {
                    while (e.MoveNext()) count++;
                }
            }
            return count;
        }

Fun tidbit, the 'checked' keyword which I rarely see.

It enables arithmetic overflow checking. Disabling below behavior by throwing an 'OverflowException'.

byte b = byte.MaxValue;
Console.WriteLine(b);       // 255  (11111111)
Console.WriteLine(++b);     // 0    (00000000)
checked
{
    byte b = byte.MaxValue;
    Console.WriteLine(b);      // b=255
    try
    {
        Console.WriteLine(++b);
    }
    catch (OverflowException e)
    {
        Console.WriteLine(e.Message);   
        // "Arithmetic operation resulted in an overflow." 
        // b = 255
    }
}

I thought this was an interesting tidbit, and something worth knowing especially with how often these IEnumerable and Linq APIs are used throughout real code bases out there.

This article was updated on June 12, 2021