Skip to content

Conversation

thibault-reigner
Copy link

@thibault-reigner thibault-reigner commented Jun 23, 2022

Bugfix

Fixes bug where calling SkipLast(0) over a custom sequence (not being a AsyncIteratorBase :

if (source is AsyncIteratorBase<TSource>)
)
would result in Corecalling Dequeue over an empty queue, as queue.Count == count (both value being equal to 0)

As a fix, I rewrote the Core method using await foreach and matching its synchronous implementation for IEnumerable as this version, aside being easier to read, also handles this particular case.

private static IEnumerable<TSource> SkipLastCore<TSource>(this IEnumerable<TSource> source, int count)

I have also edited the unit test SkipLast_Zero_NoAlias so this one fails with current version of the code.

@danielcweber
Copy link
Collaborator

danielcweber commented Jun 28, 2022

Could this, instead, throw early for invalid values of count resp. just return the identity enumerable for count == 0 ?

@thibault-reigner
Copy link
Author

thibault-reigner commented Jun 28, 2022

Could this, instead, throw early for invalid values of count resp. just return the identity enumerable for count == 0 ?

If I am not mistaken this is already handled in the SkipLast method itself, Core only being called if the value of count is valid, or if there is a need to really return a new enumerable as stated in the comment

// Return source if not actually skipping, but only if it's a type from here, to avoid
// issues if collections are used as keys or otherwise must not be aliased.
which the test
also enforces.

@danielcweber
Copy link
Collaborator

danielcweber commented Jun 29, 2022

I see...so there is a case in which we have to return source unchanged but have to 'hide' the object identity. I wasn't aware of that.

I am thinking about not touching the implementation of SkipLast, but instead having a generic way to wrap source but keep its behaviour. AsAsyncEnumerable comes to mind, but that'll only return the object itself.

Is there more cases like this one, where Core is called only to hide source? If so, could you provide a more generic solution, like e.g. a Wrap method? I think that would be the cleanest solution because we wouldn't touch any implementations and correctness of the changes would be immediately obvious.

In any case, good find!

@thibault-reigner
Copy link
Author

thibault-reigner commented Jun 29, 2022

I see...so there is a case in which we have to return source unchanged but have to 'hide' the object identity. I wasn't aware of that.

I am thinking about not touching the implementation of SkipLast, but instead having a generic way to wrap source but keep its behaviour. AsAsyncEnumerable comes to mind, but that'll only return the object itself.

Is there more cases like this one, where Core is called only to hide source? If so, could you provide a more generic solution, like e.g. a Wrap method? I think that would be the cleanest solution because we wouldn't touch any implementations and correctness of the changes would be immediately obvious.

In any case, good find!

I see...so there is a case in which we have to return source unchanged but have to 'hide' the object identity. I wasn't aware of that.

I am thinking about not touching the implementation of SkipLast, but instead having a generic way to wrap source but keep its behaviour. AsAsyncEnumerable comes to mind, but that'll only return the object itself.

Is there more cases like this one, where Core is called only to hide source? If so, could you provide a more generic solution, like e.g. a Wrap method? I think that would be the cleanest solution because we wouldn't touch any implementations and correctness of the changes would be immediately obvious.

In any case, good find!

This seems to be a bit out of scope of this PR and quite some work, as it requires to search for such pattern in all extensions methods of IAsyncEnumerable.

Also, I noticed that there is an extensive use of GetAsyncEnumerator() + MoveNextAsync() instead of await foreach in this library, most likely due to the fact it predates the introduction of IAsyncEnumerable and C# 8 await foreach.
It led to extra complexity, and here bug, with no easy way to compare methods with their synchronous variations for IEnumerable (which here was bug-free), so maybe a transition would highlight some extra bugs but also potential factorization.

Do you think such rework would be worth it to spot places where some simplfying logic such as the Wrap you suggest are relevant ?

@thibault-reigner
Copy link
Author

@danielcweber Requested changes were done in e818952

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants