Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix tiny regression in Enumerable.ElementAt #99437

Merged
merged 1 commit into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,7 @@ public override int GetCount(bool onlyIfCheap)
}

index--;
return
_source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
TryGetElementAtNonIterator(_source, index, out found);
return _source.TryGetElementAt(index, out found);
}

return base.TryGetElementAt(index, out found);
Expand Down
47 changes: 27 additions & 20 deletions src/libraries/System.Linq/src/System/Linq/ElementAt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@ public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int i
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

TSource? element = TryGetElementAt(source, index, out bool found, guardIListLength: false);
if (source is IList<TSource> list)
{
return list[index];
}

bool found;
TSource? element =
#if !OPTIMIZE_FOR_SIZE
source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
#endif
TryGetElementAtNonIterator(source, index, out found);

if (!found)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
Expand Down Expand Up @@ -102,31 +113,27 @@ public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, Index
return element;
}

private static TSource? TryGetElementAt<TSource>(this IEnumerable<TSource> source, int index, out bool found, bool guardIListLength = true) =>
private static TSource? TryGetElementAt<TSource>(this IEnumerable<TSource> source, int index, out bool found)
{
if (source is IList<TSource> list)
{
return (found = (uint)index < (uint)list.Count) ?
list[index] :
default;
}

return
#if !OPTIMIZE_FOR_SIZE
source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
#endif
TryGetElementAtNonIterator(source, index, out found, guardIListLength);
TryGetElementAtNonIterator(source, index, out found);
}

private static TSource? TryGetElementAtNonIterator<TSource>(IEnumerable<TSource> source, int index, out bool found, bool guardIListLength = true)
private static TSource? TryGetElementAtNonIterator<TSource>(IEnumerable<TSource> source, int index, out bool found)
{
Debug.Assert(source is not null);

if (source is IList<TSource> list)
{
// Historically, ElementAt would simply delegate to IList[int] without first checking the bounds.
// That in turn meant that whatever exception the IList[int] throws for out-of-bounds access would
// propagate, e.g. ImmutableArray throws IndexOutOfRangeException whereas List throws ArgumentOutOfRangeException.
// Other uses of this, though, do need to guard, such as ElementAtOrDefault and all the various
// internal TryGetElementAt helpers. So, we have a guardIListLength parameter to allow the caller
// to specify whether to guard or not.
if (!guardIListLength || (uint)index < (uint)list.Count)
{
found = true;
return list[index];
}
}
else if (index >= 0)
if (index >= 0)
{
using IEnumerator<TSource> e = source.GetEnumerator();
while (e.MoveNext())
Expand Down