As mentioned at dotnet/corefx#2237 and #14792, there are methods of Enumerable that can benefit from short-circuiting.
Now, logically a great many could such as e.g. Max(this IEnumerable<int>) testing for int.MaxValue, but that would require an additional check that would penalise all sequences that didn't have that value.
However:
- The logical behaviour of
Single and SingleOrDefault is "look for a match, then check there isn't another" (just like Where(pred).Single()). Following that rather than keeping count removes work from non-error paths as well as shortening error paths.
Min/MinOrDefault already have to check for NaN explicitly, so they don't suffer from any further work being done.
- Adding a check for
IList<T> sources to Last/LastOrDefault adds only a single check to the entire operation. The search can then proceed in reverse order and break on the first match.
These changes all affect existing behaviour, in that if either the enumerable expected to be consumed, or they were used with a predicate that had a side-effect, that behaviour would now change.
On the one hand, any change to observed behaviour bar pure performance increase is bad.
On the other hand:
- If you have side-effects in a
Func you are going to have problems elsewhere, particularly elsewhere in Linq which tends to short-circuit where it can.
- If you depend on an enumerable being consumed you are going to have problems elsewhere, , particularly elsewhere in Linq which tends to short-circuit where it can.
- The idea of Linq being side-effect free is supported not just in the documentation, but as a justification for the lack of a
ForEach outside of cases (parallel, asynchronous) where some other feature is added (eh, being parallel or asynchronous).
- The current behaviour would be confusing to someone depending on side-effects anyway: Why does
Single(pred) behave differently to Where(pred).Single()?
- The behaviour of
Single can exasperate bugs. (I had a case where certain values would throw a sequence into an incorrect repetition. Rather than Single() throwing at this bug, it hung).
Side-benefit: I noticed when working on this that not only do the current Min(IEnumerable<double>) etc. fail to take the short-circuit opportunity if it encounters a NaN, but they can actually perform much slower on a sequence with a NaN in the middle than one without, presumably due to branch mis-prediction. It's reasonable to believe that there may be other branch mis-prediction cases that such short-circuiting would help remove.
As mentioned at dotnet/corefx#2237 and #14792, there are methods of
Enumerablethat can benefit from short-circuiting.Now, logically a great many could such as e.g.
Max(this IEnumerable<int>)testing forint.MaxValue, but that would require an additional check that would penalise all sequences that didn't have that value.However:
SingleandSingleOrDefaultis "look for a match, then check there isn't another" (just likeWhere(pred).Single()). Following that rather than keeping count removes work from non-error paths as well as shortening error paths.Min/MinOrDefaultalready have to check forNaNexplicitly, so they don't suffer from any further work being done.IList<T>sources toLast/LastOrDefaultadds only a single check to the entire operation. The search can then proceed in reverse order and break on the first match.These changes all affect existing behaviour, in that if either the enumerable expected to be consumed, or they were used with a predicate that had a side-effect, that behaviour would now change.
On the one hand, any change to observed behaviour bar pure performance increase is bad.
On the other hand:
Funcyou are going to have problems elsewhere, particularly elsewhere in Linq which tends to short-circuit where it can.ForEachoutside of cases (parallel, asynchronous) where some other feature is added (eh, being parallel or asynchronous).Single(pred)behave differently toWhere(pred).Single()?Singlecan exasperate bugs. (I had a case where certain values would throw a sequence into an incorrect repetition. Rather thanSingle()throwing at this bug, it hung).Side-benefit: I noticed when working on this that not only do the current
Min(IEnumerable<double>)etc. fail to take the short-circuit opportunity if it encounters aNaN, but they can actually perform much slower on a sequence with aNaNin the middle than one without, presumably due to branch mis-prediction. It's reasonable to believe that there may be other branch mis-prediction cases that such short-circuiting would help remove.