Does the C# LINQ Aggregate method finish execution once the result can no longer change?

Viewed 1125

I know that if I chain && or || operators together in the same statement, c# will stop computing the statement and return the appropriate result if it evaluates an expression and no matter what the following expressions are, the result won't change. For instance:

var result = false && foo() && bar();

In this statement, foo() and bar() will never get executed since the first expression is false. My question is, will the Enumerable.Aggregate<TSource, TAccumulate> do the same thing when ran over a list of bools or will it evaluate all the expressions regardless? For example:

var result = new List<bool>
{
    false,
    foo(),
    bar()
}.Aggregate(true, (acc, x) => acc && x);
4 Answers

Pursuant to a different issue, I was using Aggregate and needed to exit early if a certain intermediate value occurred, which ended up involving a lot of ternary operators (or a lambda body with if) to bypass the aggregation when that value was reached, so I wrote some new extensions to exit early:

public static T AggregateWhile<T>(this IEnumerable<T> src, Func<T, T, T> accumFn, Predicate<T> whileFn) {
    using (var e = src.GetEnumerator()) {
        if (!e.MoveNext())
            throw new Exception("At least one element required by AggregateWhile");
        var ans = e.Current;
        while (whileFn(ans) && e.MoveNext())
            ans = accumFn(ans, e.Current);
        return ans;
    }
}

public static TAccum AggregateWhile<TAccum, T>(this IEnumerable<T> src, TAccum seed, Func<TAccum, T, TAccum> accumFn, Predicate<TAccum> whileFn) {
    using (var e = src.GetEnumerator()) {
        if (!e.MoveNext())
            throw new Exception("At least one element required by AggregateWhile");
        var ans = accumFn(seed, e.Current);
        while (whileFn(ans) && e.MoveNext())
            ans = accumFn(ans, e.Current);
        return ans;
    }
}

For your example, you could use it as follows:

var result = new List<bool> {
    false,
    foo(),
    bar()
}.AggregateWhile(true, (acc, x) => acc && x, acc => acc);

Of course, foo() and bar() will be executed because a List<bool> doesn't defer execution, so only the loop time is saved. But if you had a sequence that does:

var result = new List<Func<bool>> { () => false, () => foo(), () => bar() }
             .AggregateWhile(true, (acc, f) => acc && f(), acc => acc);

that would skip executing foo and bar.

The obvious counterpart AggregateUntils are left as an exercise :)

Related