Question

Will LINQ Any() ever enumerate without arguments?

Given the following:

var nums = GetNums();
Console.WriteLine(nums.Any());
Console.WriteLine(string.Join(", ", nums));
    
static IEnumerable<int> GetNums()
{
    yield return 1;
    yield return 2;
    yield return 3;
}

ReSharper flags this as "Possible Multiple Enumeration." However, running it produces

True
1, 2, 3

proving that no elements were skipped over. Will this always be the case when calling Any without a predicate? Or are there cases where this will produce unwanted side effects?

 2  69  2
1 Jan 1970

Solution

 5

It entirely depends on what's backing the IEnumerable<T> and how it operates, but you will iterate over it multiple times. Some enumerables will result in different results each time you enumerate, some won't (as in your example).

For example, BlockingCollection<T> allows you to obtain a "consuming enumerable" which yields items only once.

Example code:

var blockingCollection = new BlockingCollection<int>();
blockingCollection.Add(1);
blockingCollection.Add(2);
blockingCollection.Add(3);
blockingCollection.CompleteAdding();

var nums = blockingCollection.GetConsumingEnumerable();
Console.WriteLine(nums.Any());
Console.WriteLine(string.Join(", ", nums));
Console.WriteLine(nums.Any());

Output:

True
2, 3
False

Try it online

Another possible issue is that a second enumeration of nums could execute a (potentially heavy) query against a database a second time, if the IEnumerable<T> represents the results of a database query. This could both result in additional unnecessary work, tying up unnecessary resources of your application and your database server.

The data could also change between queries. Imagine this scenario:

  1. Someone inserts a record into your database.
  2. You check nums.Any() which makes one query, and indicates that there is a matching record.
  3. That record gets deleted.
  4. You call string.Join(", ", nums) but its now empty because there are no records in the database the second time the query is run.

If you're intending to use an enumerable multiple times, or you're worried the data could change between enumerations, you can materialise it to an array, list, or other in-memory collection. Note that .ToList() will iterate over the enumerable to produce the list.

For example:

var blockingCollection = new BlockingCollection<int>();
blockingCollection.Add(1);
blockingCollection.Add(2);
blockingCollection.Add(3);
blockingCollection.CompleteAdding();

var nums = blockingCollection.GetConsumingEnumerable()
    .ToList(); // put the values in a list
Console.WriteLine(nums.Any());
Console.WriteLine(string.Join(", ", nums));
Console.WriteLine(nums.Any());

Output:

True
1, 2, 3
True
2024-07-23
ProgrammingLlama

Solution

 2

Any will enumerate the IEnumerable<T>.

In this case, GetNums just so happens to return an IEnumerable<int> object, whose GetEnumerator() method returns a new enumerator, that is reset back to the initial state.

Here are some excerpts of the decompiled code that https://sharplab.io/ generates, which makes it very clear what's going on.

// GetNums returns an instance of type <GetNums>d__0, with a state of -2
internal static IEnumerable<int> GetEnums()
{
    return new <GetNums>d__0(-2);
}
// After Any() consumes the first element of the enumerator, the state is no longer -2
// so GetEnumerator in <GetNums>d__0 returns 'new <GetNums>d__0(0)', which has a state of 0.
// this is as if GetEnums has not been executed at all
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
    if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
    {
        <>1__state = 0;
        return this;
    }
    return new <GetNums>d__0(0);
}

Reading the spec, I can't see where this behaviour is specified, so another implementation may very well choose not to have this behaviour.

If GetNums were not a pure function, and it yield return SomeSideEffect() instead, SomeSideEffect() will be called twice, and might end up yielding a different value the second time.


To show that Any() does call MoveNext on the enumerator, you can write your own type that implements IEnumerable<T> and IEnumerator<T>, and in the GetEnumerator method, return this (unlike what the compiler generates for an iterator block).

class OneToFive: IEnumerable<int>, IEnumerator<int> {
    public IEnumerator<int> GetEnumerator() => this;
    IEnumerator IEnumerable.GetEnumerator() => this;
    public int Current { get; set; }
    object IEnumerator.Current => Current;
    public bool MoveNext() {
        if (Current > 4) return false;
        Current++;
        return true;
    }
    public void Reset() { Current = 0; }
    public void Dispose() {}
}
var nums = new OneToFive();
Console.WriteLine(nums.Any()); // True
Console.WriteLine(string.Join(", ", nums)); // 2, 3, 4, 5
2024-07-23
Sweeper