Skip to content
Open
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
26 changes: 2 additions & 24 deletions src/libraries/System.Linq/src/System/Linq/AnyAll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,9 @@ public static partial class Enumerable
{
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

if (source is ICollection<TSource> gc)
{
return gc.Count != 0;
}

if (!IsSizeOptimized && source is Iterator<TSource> iterator)
{
int count = iterator.GetCount(onlyIfCheap: true);
if (count >= 0)
{
return count != 0;
}

iterator.TryGetFirst(out bool found);
return found;
}

if (source is ICollection ngc)
if (TryGetNonEnumeratedCount(source, out int nonEnumeratedCount))
{
return ngc.Count != 0;
return nonEnumeratedCount != 0;
}

using IEnumerator<TSource> e = source.GetEnumerator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,17 @@ public override List<TSource> ToList()

public override int GetCount(bool onlyIfCheap)
{
if (_source is Iterator<TSource> iterator)
if (!TryGetNonEnumeratedCount(_source, out int count))
{
int count = iterator.GetCount(onlyIfCheap);
return count == -1 ? -1 : checked(count + 1);
if (onlyIfCheap)
{
return -1;
}

count = _source.Count();
}

return !onlyIfCheap || _source is ICollection<TSource> ? checked(_source.Count() + 1) : -1;
return checked(count + 1);
}

public override TSource? TryGetFirst(out bool found)
Expand Down Expand Up @@ -274,13 +278,17 @@ public override List<TSource> ToList()

public override int GetCount(bool onlyIfCheap)
{
if (_source is Iterator<TSource> iterator)
if (!TryGetNonEnumeratedCount(_source, out int count))
{
int count = iterator.GetCount(onlyIfCheap);
return count == -1 ? -1 : checked(count + _appendCount + _prependCount);
if (onlyIfCheap)
{
return -1;
}

count = _source.Count();
}

return !onlyIfCheap || _source is ICollection<TSource> ? checked(_source.Count() + _appendCount + _prependCount) : -1;
return checked(count + _appendCount + _prependCount);
}

public override bool Contains(TSource value)
Expand Down
29 changes: 7 additions & 22 deletions src/libraries/System.Linq/src/System/Linq/Count.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,9 @@ public static partial class Enumerable
{
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

if (source is ICollection<TSource> collectionoft)
{
return collectionoft.Count;
}

if (source is Iterator<TSource> iterator)
{
return iterator.GetCount(onlyIfCheap: false);

@skyoxZ skyoxZ Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes it is possible to avoid iterating over all elements even when TryGetNonEnumeratedCount fails:

int count = new[] { 1, 2, 3 }.Distinct().Concat(Enumerable.Repeat(1, 999999)).Count();

The PR introduces a regression for such case.

}

if (source is ICollection collection)
if (TryGetNonEnumeratedCount(source, out int nonEnumeratedCount))
{
return collection.Count;
return nonEnumeratedCount;
}

int count = 0;
Expand Down Expand Up @@ -102,11 +87,6 @@ public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource,
/// </remarks>
public static bool TryGetNonEnumeratedCount<TSource>(this IEnumerable<TSource> source, out int count)
{
if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

if (source is ICollection<TSource> collectionoft)
{
count = collectionoft.Count;
Expand All @@ -129,6 +109,11 @@ public static bool TryGetNonEnumeratedCount<TSource>(this IEnumerable<TSource> s
return true;
}

if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
Comment on lines +112 to +115

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving this null check below the type checks is a very minor optimisation for the case where any of the optimised types are provided.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does moving it improve such cases? The null check has to be performed regardless, no?


count = 0;
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ public override List<TSource> ToList()

public override int GetCount(bool onlyIfCheap)
{
int count;
if (!onlyIfCheap || _source is ICollection<TSource> || _source is ICollection)
if (!TryGetNonEnumeratedCount(_source, out int count))
{
if (onlyIfCheap)
{
return -1;
}

count = _source.Count();
}
else
{
count = _source is Iterator<TSource> iterator ? iterator.GetCount(onlyIfCheap: true) : -1;
}

return count == 0 ? 1 : count;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,17 @@ private void Fill(TElement[] buffer, Span<TElement> destination)

public override int GetCount(bool onlyIfCheap)
{
if (_source is Iterator<TElement> iterator)
if (!TryGetNonEnumeratedCount(_source, out int count))
{
return iterator.GetCount(onlyIfCheap);
if (onlyIfCheap)
{
return -1;
}

count = _source.Count();
}

return !onlyIfCheap || _source is ICollection<TElement> || _source is ICollection ? _source.Count() : -1;
return count;
}

internal TElement[] ToArray(int minIdx, int maxIdx)
Expand Down
68 changes: 0 additions & 68 deletions src/libraries/System.Linq/tests/SelectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,6 @@ namespace System.Linq.Tests
{
public class SelectTests : EnumerableTests
{
[Fact]
public void SelectSideEffectsExecutedOnCount()
{
int i = 0;
// If we made no promises about side effects, i could be 0, but in practice users have
// taken a dependency on side effects executing on Count.
var count = Enumerable.Range(1, 10).Select(x => i++).Count();
Assert.Equal(10, count);
Assert.Equal(10, i);

i = 0;
count = Enumerable.Range(1, 10).Skip(5).Select(x => i++).Count();
Assert.Equal(5, count);
Assert.Equal(5, i);

i = 0;
count = Enumerable.Range(1, 10).Take(5).Select(x => i++).Count();
Assert.Equal(5, count);
Assert.Equal(5, i);

i = 0;
count = Enumerable.Range(1, 10).Skip(2).Take(3).Select(x => i++).Count();
Assert.Equal(3, count);
Assert.Equal(3, i);
}

[Fact]
public void SameResultsRepeatCallsStringQuery()
{
Expand Down Expand Up @@ -1220,48 +1194,6 @@ public static IEnumerable<object[]> MoveNextAfterDisposeData()
yield return [Enumerable.Range(1, 30)];
}

[Theory]
[MemberData(nameof(RunSelectorDuringCountData))]
public void RunSelectorDuringCount(IEnumerable<int> source)
{
int timesRun = 0;
var selected = source.Select(i => timesRun++);
selected.Count();

Assert.Equal(source.Count(), timesRun);
}

public static IEnumerable<object[]> RunSelectorDuringCountData()
{
var transforms = new Func<IEnumerable<int>, IEnumerable<int>>[]
{
e => e,
e => ForceNotCollection(e),
e => ForceNotCollection(e).Skip(1),
e => ForceNotCollection(e).Where(i => true),
e => e.ToArray().Where(i => true),
e => e.ToList().Where(i => true),
e => new LinkedList<int>(e).Where(i => true),
e => e.Select(i => i),
e => e.Take(e.Count()),
e => e.ToArray(),
e => e.ToList(),
e => new LinkedList<int>(e) // Implements IList<T>.
};

var r = new Random(42);

for (int i = 0; i <= 5; i++)
{
var enumerable = Enumerable.Range(1, i).Select(_ => r.Next());

foreach (var transform in transforms)
{
yield return [transform(enumerable)];
}
}
}

[Fact]
public void Select_SourceIsIList_EnumeratorDisposedOnComplete()
{
Expand Down
Loading