Skip to content

Introduce size-optimized IListSelect iterator #118156

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion src/libraries/System.Linq/src/System.Linq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<Compile Include="System\Linq\InfiniteSequence.cs" />
<Compile Include="System\Linq\Intersect.cs" />
<Compile Include="System\Linq\Iterator.cs" />
<Compile Include="System\Linq\Iterator.SizeOpt.cs" />
<Compile Include="System\Linq\Iterator.SpeedOpt.cs" />
<Compile Include="System\Linq\Join.cs" />
<Compile Include="System\Linq\Last.cs" />
Expand Down Expand Up @@ -70,7 +71,6 @@
<Compile Include="System\Linq\Single.cs" />
<Compile Include="System\Linq\SingleLinkedNode.cs" />
<Compile Include="System\Linq\Skip.cs" />
<Compile Include="System\Linq\Skip.SizeOpt.cs" />
<Compile Include="System\Linq\Skip.SpeedOpt.cs" />
<Compile Include="System\Linq\SkipTake.SpeedOpt.cs" />
<Compile Include="System\Linq\Sum.cs" />
Expand Down
97 changes: 97 additions & 0 deletions src/libraries/System.Linq/src/System/Linq/Iterator.SizeOpt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

namespace System.Linq;

public static partial class Enumerable
{
/// <summary>
/// An iterator that implements <see cref="IList{T}"/>. This is used primarily in size-optimized
/// code to turn linear-time iterators into constant-time iterators. The primary cost is
/// additional type checks, which are small compared to generic virtual calls.
/// </summary>
private sealed class SizeOptIListSelectIterator<TSource, TResult>(IList<TSource> _source, Func<TSource, TResult> _selector)
: Iterator<TResult>, IList<TResult>
{
TResult IList<TResult>.this[int index]
{
get => _selector(_source[index]);
set => ThrowHelper.ThrowNotSupportedException();
}

int ICollection<TResult>.Count => _source.Count;
bool ICollection<TResult>.IsReadOnly => true;

void ICollection<TResult>.Add(TResult item) => ThrowHelper.ThrowNotSupportedException();
void ICollection<TResult>.Clear() => ThrowHelper.ThrowNotSupportedException();
bool ICollection<TResult>.Contains(TResult item)
=> IndexOf(item) >= 0;

int IList<TResult>.IndexOf(TResult item) => IndexOf(item);

private int IndexOf(TResult item)
{
for (int i = 0; i < _source.Count; i++)
{
if (EqualityComparer<TResult>.Default.Equals(_selector(_source[i]), item))
{
return i;
}
}
return -1;
}

void ICollection<TResult>.CopyTo(TResult[] array, int arrayIndex)
{
for (int i = 0; i < _source.Count; i++)
{
array[arrayIndex + i] = _selector(_source[i]);
}
}

void IList<TResult>.Insert(int index, TResult item) => ThrowHelper.ThrowNotSupportedException();
bool ICollection<TResult>.Remove(TResult item) => ThrowHelper.ThrowNotSupportedException_Boolean();
void IList<TResult>.RemoveAt(int index) => ThrowHelper.ThrowNotSupportedException();

private protected override Iterator<TResult> Clone()
=> new SizeOptIListSelectIterator<TSource, TResult>(_source, _selector);

public override bool MoveNext()
{
var source = _source;
int index = _state - 1;
if ((uint)index < (uint)source.Count)
{
_state++;
_current = _selector(source[index]);
return true;
}

Dispose();
return false;
}

public override TResult[] ToArray()
{
TResult[] array = new TResult[_source.Count];
for (int i = 0; i < _source.Count; i++)
{
array[i] = _selector(_source[i]);
}
return array;
}
public override List<TResult> ToList()
{
List<TResult> list = new List<TResult>(_source.Count);
for (int i = 0; i < _source.Count; i++)
{
list.Add(_selector(_source[i]));
}
return list;
}
public override int GetCount(bool onlyIfCheap) => _source.Count;
}
}
5 changes: 5 additions & 0 deletions src/libraries/System.Linq/src/System/Linq/Select.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices.Marshalling;
using static System.Linq.Utilities;

namespace System.Linq
Expand Down Expand Up @@ -32,6 +33,10 @@ public static IEnumerable<TResult> Select<TSource, TResult>(
// don't need more code, just more data structures describing the new types).
if (IsSizeOptimized && typeof(TResult).IsValueType)
{
if (source is IList<TSource> il)
{
return new SizeOptIListSelectIterator<TSource, TResult>(il, selector);
}
return new IEnumerableSelectIterator<TSource, TResult>(iterator, selector);
}
else
Expand Down
20 changes: 0 additions & 20 deletions src/libraries/System.Linq/src/System/Linq/Skip.SizeOpt.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/libraries/System.Linq/src/System/Linq/Skip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> sourc
return iterator.Skip(count) ?? Empty<TSource>();
}

return IsSizeOptimized ? SizeOptimizedSkipIterator(source, count) : SpeedOptimizedSkipIterator(source, count);
return SpeedOptimizedSkipIterator(source, count);
}

public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
Expand Down
11 changes: 0 additions & 11 deletions src/libraries/System.Linq/src/System/Linq/Take.SizeOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,6 @@ namespace System.Linq
{
public static partial class Enumerable
{
private static IEnumerable<TSource> SizeOptimizedTakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
Debug.Assert(count > 0);

foreach (TSource element in source)
{
yield return element;
if (--count == 0) break;
}
}

private static IEnumerable<TSource> SizeOptimizedTakeRangeIterator<TSource>(IEnumerable<TSource> source, int startIndex, int endIndex)
{
Debug.Assert(source is not null);
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Linq/src/System/Linq/Take.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> sourc
return [];
}

return IsSizeOptimized ? SizeOptimizedTakeIterator(source, count) : SpeedOptimizedTakeIterator(source, count);
return SpeedOptimizedTakeIterator(source, count);
}

/// <summary>Returns a specified range of contiguous elements from a sequence.</summary>
Expand Down
Loading