Repository navigation
Make the SkipLast polyfill streaming - #692
Merged
Merged
Conversation
SkipLast was Reverse().Skip(count).Reverse(), which buffered the whole source twice before yielding anything. Use the queue based range iterator, as the BCL does.
This was referenced Oct 12, 2026
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
SkipLastpolyfill (.NET Framework / netstandard2.0) wastarget.Reverse().Skip(count).Reverse(). That pulls the entire source and buffers it twice before the first item is yielded, sosource.SkipLast(3).Take(3)over 1,000,000 items enumerated all 1,000,000.It now uses the existing
TakeRangeFromEndIteratorhelper (range0..^count), which is what the BCL does:ICollection<T>/ICollection), it yields the firstCount - countitems and stops;countitems.count <= 0returnstarget.Skip(0)as the BCL does. A null source still throwsArgumentNullException(parameter namesource, as before) whenSkipLastis called, and execution is still deferred.Results are identical. The difference is in when the source is pulled: items are yielded as they become available, where before the whole source was consumed on the first
MoveNext.Measurements
net48, Release, x64, Stopwatch, best of 5, microseconds per call:
SkipLast(3).Take(3)over a 1,000,000 item iteratorSkipLast(3)over aList<int>of 1,000,000, fully enumeratedSkipLast(3)over a 1,000,000 item iterator, fully enumeratedSkipLast(3)over aList<int>of 20SkipLast(3)over a 20 item iteratorFull enumeration costs about the same time, without the two full-size buffers (the queue holds 3 items here).
Tests
Added to
PolyfillTests_IEnumerable.cs:int.MinValuetoint.MaxValueagainst an array,List<T>,Queue<T>,HashSet<T>and an iterator, for lengths 0, 1, 2 and 10, including enumerating the result twice;SkipLast(3).Take(3)over a 1,000,000 item iterator pulls at most 7 items;The deferred-execution test skips its list assertion on .NET 5 (checked at run time): the real SkipLast there fixes its bounds over a list when it is called, which .NET 6 changed.