When does IEnumerable.Any(Func) return a value?

2019-07-20 04:01发布

问题:

I recently saw a bit of code in a codebase I work with, where ReSharper offered to refactor it to collection.Any(Func< bool >).

I'm wondering about the performance impacts of this. Say I have a call that looks like this:

bool hasEvenValue = collection.Any(i => (i % 2) == 0);

...And data that looks like this...

{ 1, 2, 3, 5, 3, 5, 1, 3, 5, 2 }

When does Enumerable.Any() return the value? The second data element, or will it process every single element before returning true, in this instance?

回答1:

It returns as soon as it sees a matching element, or if none it processes the whole sequence.

For that reason it is better than using .Count(...) != 0 (also more readable and semantically meaningful).



回答2:

Here's the implementation of IEnumerable<T>.Any(...) (uncompiled with dotKeep):

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
  if (source == null)
    throw Error.ArgumentNull("source");
  if (predicate == null)
    throw Error.ArgumentNull("predicate");
  foreach (TSource source1 in source)
  {
    if (predicate(source1))
      return true;
  }
  return false;
}

So basically it returns as soon as an item satisfies the condition.



回答3:

When does IEnumerable.Any(Func) return a value?

Always and immediately when it's executed since it is not deferred executed. It returns a boolean which indicates whether or not one of the elements in the sequence returns true for the given predicate. Hence it doesn't need to execute the whole query unlike Count.



回答4:

Here's some code I whipped up in LINQPad to illustrate that the Any operator terminates after it hits the first match.

void Main()
{
    Console.WriteLine("Are there any evens? " + YieldEvenThenThrowEnumerable().Any(i => i % 2 == 0));
    Console.WriteLine("still running");
    Console.WriteLine("Are there any odds? " + YieldEvenThenThrowEnumerable().Any(i => i % 2 == 1));
    Console.WriteLine("never reaches this point");
}

IEnumerable<int> YieldEvenThenThrowEnumerable()
{
    yield return 2;
    throw new InvalidOperationException("TEST");
}

Which outputs:

Are there any evens?
True
still running
< an InvalidOperationException is thrown from enumerable at this point >

If the first Any call that tests "if any are even" walked the entire enumerable, the program would have terminated without displaying any messages. The second call was put in place to illustrate that the "if any are odd" test walks the entire list resulting in the exception being thrown.