diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs
deleted file mode 100644
index 13970c6a0fc22b..00000000000000
--- a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Linq;
-
-namespace System.Collections.Generic
-{
- ///
- /// Internal helper functions for working with enumerables.
- ///
- internal static partial class EnumerableHelpers
- {
- ///
- /// Copies items from an enumerable to an array.
- ///
- /// The element type of the enumerable.
- /// The source enumerable.
- /// The destination array.
- /// The index in the array to start copying to.
- /// The number of items in the enumerable.
- internal static void Copy(IEnumerable source, T[] array, int arrayIndex, int count)
- {
- Debug.Assert(source != null);
- Debug.Assert(arrayIndex >= 0);
- Debug.Assert(count >= 0);
- Debug.Assert(array.Length - arrayIndex >= count);
-
- if (source is ICollection collection)
- {
- Debug.Assert(collection.Count == count);
- collection.CopyTo(array, arrayIndex);
- return;
- }
-
- IterativeCopy(source, array, arrayIndex, count);
- }
-
- ///
- /// Copies items from a non-collection enumerable to an array.
- ///
- /// The element type of the enumerable.
- /// The source enumerable.
- /// The destination array.
- /// The index in the array to start copying to.
- /// The number of items in the enumerable.
- internal static void IterativeCopy(IEnumerable source, T[] array, int arrayIndex, int count)
- {
- Debug.Assert(source != null && !(source is ICollection));
- Debug.Assert(arrayIndex >= 0);
- Debug.Assert(count >= 0);
- Debug.Assert(array.Length - arrayIndex >= count);
-
- int endIndex = arrayIndex + count;
- foreach (T item in source)
- {
- array[arrayIndex++] = item;
- }
-
- Debug.Assert(arrayIndex == endIndex);
- }
-
- /// Converts an enumerable to an array.
- /// The enumerable to convert.
- /// The resulting array.
- internal static T[] ToArray(IEnumerable source)
- {
- Debug.Assert(source != null);
-
- if (source is ICollection collection)
- {
- int count = collection.Count;
- if (count == 0)
- {
- return Array.Empty();
- }
-
- var result = new T[count];
- collection.CopyTo(result, arrayIndex: 0);
- return result;
- }
-
- LargeArrayBuilder builder = new();
- builder.AddRange(source);
- return builder.ToArray();
- }
- }
-}
diff --git a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SizeOpt.cs b/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SizeOpt.cs
deleted file mode 100644
index 50ac7317090160..00000000000000
--- a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SizeOpt.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-
-namespace System.Collections.Generic
-{
- // LargeArrayBuilder.netcoreapp.cs provides a "LargeArrayBuilder" that's meant to help
- // avoid allocations and copying while building up an array. But in doing so, it utilizes
- // T[][] to store T[]s, which results in significant size increases for AOT builds. To
- // address that, this minimal wrapper for ArrayBuilder may be used instead; it's a simple
- // passthrough to ArrayBuilder, and thus doesn't incur the size increase due to the T[][]s.
-
- internal struct LargeArrayBuilder
- {
- private ArrayBuilder _builder; // mutable struct; do not make this readonly
-
- ///
- /// Constructs a new builder.
- ///
- public LargeArrayBuilder() => this = default;
-
- public int Count => _builder.Count;
-
- public void Add(T item) => _builder.Add(item);
-
- public void AddRange(IEnumerable items)
- {
- Debug.Assert(items != null);
- foreach (T item in items)
- {
- _builder.Add(item);
- }
- }
-
- public T[] ToArray() => _builder.ToArray();
-
- public CopyPosition CopyTo(CopyPosition position, T[] array, int arrayIndex, int count)
- {
- Array.Copy(_builder.Buffer!, position.Column, array, arrayIndex, count);
- return new CopyPosition(0, position.Column + count);
- }
- }
-}
diff --git a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SpeedOpt.cs
deleted file mode 100644
index 99458c41dfe9aa..00000000000000
--- a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.SpeedOpt.cs
+++ /dev/null
@@ -1,340 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-
-namespace System.Collections.Generic
-{
- ///
- /// Helper type for building dynamically-sized arrays while minimizing allocations and copying.
- ///
- /// The element type.
- internal struct LargeArrayBuilder
- {
- private const int StartingCapacity = 4;
- private const int ResizeLimit = 8;
-
- private readonly int _maxCapacity; // The maximum capacity this builder can have.
- private T[] _first; // The first buffer we store items in. Resized until ResizeLimit.
- private ArrayBuilder _buffers; // After ResizeLimit * 2, we store previous buffers we've filled out here.
- private T[] _current; // Current buffer we're reading into. If _count <= ResizeLimit, this is _first.
- private int _index; // Index into the current buffer.
- private int _count; // Count of all of the items in this builder.
-
- ///
- /// Constructs a new builder.
- ///
- public LargeArrayBuilder()
- : this(maxCapacity: int.MaxValue)
- {
- }
-
- ///
- /// Constructs a new builder with the specified maximum capacity.
- ///
- /// The maximum capacity this builder can have.
- ///
- /// Do not add more than items to this builder.
- ///
- public LargeArrayBuilder(int maxCapacity)
- {
- Debug.Assert(maxCapacity >= 0);
-
- this = default;
- _first = _current = Array.Empty();
- _maxCapacity = maxCapacity;
- }
-
- ///
- /// Gets the number of items added to the builder.
- ///
- public int Count => _count;
-
- ///
- /// Adds an item to this builder.
- ///
- /// The item to add.
- ///
- /// Use if adding to the builder is a bottleneck for your use case.
- /// Otherwise, use .
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Add(T item)
- {
- Debug.Assert(_maxCapacity > _count);
-
- int index = _index;
- T[] current = _current;
-
- // Must be >= and not == to enable range check elimination
- if ((uint)index >= (uint)current.Length)
- {
- AddWithBufferAllocation(item);
- }
- else
- {
- current[index] = item;
- _index = index + 1;
- }
-
- _count++;
- }
-
- // Non-inline to improve code quality as uncommon path
- [MethodImpl(MethodImplOptions.NoInlining)]
- private void AddWithBufferAllocation(T item)
- {
- AllocateBuffer();
- _current[_index++] = item;
- }
-
- ///
- /// Adds a range of items to this builder.
- ///
- /// The sequence to add.
- ///
- /// It is the caller's responsibility to ensure that adding
- /// does not cause the builder to exceed its maximum capacity.
- ///
- public void AddRange(IEnumerable items)
- {
- Debug.Assert(items != null);
-
- using (IEnumerator enumerator = items.GetEnumerator())
- {
- T[] destination = _current;
- int index = _index;
-
- // Continuously read in items from the enumerator, updating _count
- // and _index when we run out of space.
-
- while (enumerator.MoveNext())
- {
- T item = enumerator.Current;
-
- if ((uint)index >= (uint)destination.Length)
- {
- AddWithBufferAllocation(item, ref destination, ref index);
- }
- else
- {
- destination[index] = item;
- }
-
- index++;
- }
-
- // Final update to _count and _index.
- _count += index - _index;
- _index = index;
- }
- }
-
- // Non-inline to improve code quality as uncommon path
- [MethodImpl(MethodImplOptions.NoInlining)]
- private void AddWithBufferAllocation(T item, ref T[] destination, ref int index)
- {
- _count += index - _index;
- _index = index;
- AllocateBuffer();
- destination = _current;
- index = _index;
- _current[index] = item;
- }
-
- ///
- /// Copies the contents of this builder to the specified array.
- ///
- /// The destination array.
- /// The index in to start copying to.
- /// The number of items to copy.
- public void CopyTo(T[] array, int arrayIndex, int count)
- {
- Debug.Assert(arrayIndex >= 0);
- Debug.Assert(count >= 0 && count <= Count);
- Debug.Assert(array.Length - arrayIndex >= count);
-
- for (int i = 0; count > 0; i++)
- {
- // Find the buffer we're copying from.
- T[] buffer = GetBuffer(index: i);
-
- // Copy until we satisfy count, or we reach the end of the buffer.
- int toCopy = Math.Min(count, buffer.Length);
- Array.Copy(buffer, 0, array, arrayIndex, toCopy);
-
- // Increment variables to that position.
- count -= toCopy;
- arrayIndex += toCopy;
- }
- }
-
- ///
- /// Copies the contents of this builder to the specified array.
- ///
- /// The position in this builder to start copying from.
- /// The destination array.
- /// The index in to start copying to.
- /// The number of items to copy.
- /// The position in this builder that was copied up to.
- public CopyPosition CopyTo(CopyPosition position, T[] array, int arrayIndex, int count)
- {
- Debug.Assert(array != null);
- Debug.Assert(arrayIndex >= 0);
- Debug.Assert(count > 0 && count <= Count);
- Debug.Assert(array.Length - arrayIndex >= count);
-
- // Go through each buffer, which contains one 'row' of items.
- // The index in each buffer is referred to as the 'column'.
-
- /*
- * Visual representation:
- *
- * C0 C1 C2 .. C31 .. C63
- * R0: [0] [1] [2] .. [31]
- * R1: [32] [33] [34] .. [63]
- * R2: [64] [65] [66] .. [95] .. [127]
- */
-
- int row = position.Row;
- int column = position.Column;
-
- T[] buffer = GetBuffer(row);
- int copied = CopyToCore(buffer, column);
-
- if (count == 0)
- {
- return new CopyPosition(row, column + copied).Normalize(buffer.Length);
- }
-
- do
- {
- buffer = GetBuffer(++row);
- copied = CopyToCore(buffer, 0);
- } while (count > 0);
-
- return new CopyPosition(row, copied).Normalize(buffer.Length);
-
- int CopyToCore(T[] sourceBuffer, int sourceIndex)
- {
- Debug.Assert(sourceBuffer.Length > sourceIndex);
-
- // Copy until we satisfy `count` or reach the end of the current buffer.
- int copyCount = Math.Min(sourceBuffer.Length - sourceIndex, count);
- Array.Copy(sourceBuffer, sourceIndex, array, arrayIndex, copyCount);
-
- arrayIndex += copyCount;
- count -= copyCount;
-
- return copyCount;
- }
- }
-
- ///
- /// Retrieves the buffer at the specified index.
- ///
- /// The index of the buffer.
- public T[] GetBuffer(int index)
- {
- Debug.Assert(index >= 0 && index < _buffers.Count + 2);
-
- return index == 0 ? _first :
- index <= _buffers.Count ? _buffers[index - 1] :
- _current;
- }
-
- ///
- /// Adds an item to this builder.
- ///
- /// The item to add.
- ///
- /// Use if adding to the builder is a bottleneck for your use case.
- /// Otherwise, use .
- ///
- [MethodImpl(MethodImplOptions.NoInlining)]
- public void SlowAdd(T item) => Add(item);
-
- ///
- /// Creates an array from the contents of this builder.
- ///
- public T[] ToArray()
- {
- if (TryMove(out T[] array))
- {
- // No resizing to do.
- return array;
- }
-
- array = new T[_count];
- CopyTo(array, 0, _count);
- return array;
- }
-
- ///
- /// Attempts to transfer this builder into an array without copying.
- ///
- /// The transferred array, if the operation succeeded.
- /// true if the operation succeeded; otherwise, false.
- public bool TryMove(out T[] array)
- {
- array = _first;
- return _count == _first.Length;
- }
-
- private void AllocateBuffer()
- {
- // - On the first few adds, simply resize _first.
- // - When we pass ResizeLimit, allocate ResizeLimit elements for _current
- // and start reading into _current. Set _index to 0.
- // - When _current runs out of space, add it to _buffers and repeat the
- // above step, except with _current.Length * 2.
- // - Make sure we never pass _maxCapacity in all of the above steps.
-
- Debug.Assert((uint)_maxCapacity > (uint)_count);
- Debug.Assert(_index == _current.Length, $"{nameof(AllocateBuffer)} was called, but there's more space.");
-
- // If _count is int.MinValue, we want to go down the other path which will raise an exception.
- if ((uint)_count < (uint)ResizeLimit)
- {
- // We haven't passed ResizeLimit. Resize _first, copying over the previous items.
- Debug.Assert(_current == _first && _count == _first.Length);
-
- int nextCapacity = Math.Min(_count == 0 ? StartingCapacity : _count * 2, _maxCapacity);
-
- _current = new T[nextCapacity];
- Array.Copy(_first, _current, _count);
- _first = _current;
- }
- else
- {
- Debug.Assert(_maxCapacity > ResizeLimit);
- Debug.Assert(_count == ResizeLimit ^ _current != _first);
-
- int nextCapacity;
- if (_count == ResizeLimit)
- {
- nextCapacity = ResizeLimit;
- }
- else
- {
- // Example scenario: Let's say _count == 64.
- // Then our buffers look like this: | 8 | 8 | 16 | 32 |
- // As you can see, our count will be just double the last buffer.
- // Now, say _maxCapacity is 100. We will find the right amount to allocate by
- // doing min(64, 100 - 64). The lhs represents double the last buffer,
- // the rhs the limit minus the amount we've already allocated.
-
- Debug.Assert(_count >= ResizeLimit * 2);
- Debug.Assert(_count == _current.Length * 2);
-
- _buffers.Add(_current);
- nextCapacity = Math.Min(_count, _maxCapacity - _count);
- }
-
- _current = new T[nextCapacity];
- _index = 0;
- }
- }
- }
-}
diff --git a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.cs
deleted file mode 100644
index bf28fb607b239b..00000000000000
--- a/src/libraries/Common/src/System/Collections/Generic/LargeArrayBuilder.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-
-namespace System.Collections.Generic
-{
- ///
- /// Represents a position within a .
- ///
- [DebuggerDisplay("{DebuggerDisplay,nq}")]
- internal readonly struct CopyPosition
- {
- ///
- /// Constructs a new .
- ///
- /// The index of the buffer to select.
- /// The index within the buffer to select.
- internal CopyPosition(int row, int column)
- {
- Debug.Assert(row >= 0);
- Debug.Assert(column >= 0);
-
- Row = row;
- Column = column;
- }
-
- ///
- /// Represents a position at the start of a .
- ///
- public static CopyPosition Start => default(CopyPosition);
-
- ///
- /// The index of the buffer to select.
- ///
- internal int Row { get; }
-
- ///
- /// The index within the buffer to select.
- ///
- internal int Column { get; }
-
- ///
- /// If this position is at the end of the current buffer, returns the position
- /// at the start of the next buffer. Otherwise, returns this position.
- ///
- /// The length of the current buffer.
- public CopyPosition Normalize(int endColumn)
- {
- Debug.Assert(Column <= endColumn);
-
- return Column == endColumn ?
- new CopyPosition(Row + 1, 0) :
- this;
- }
-
- ///
- /// Gets a string suitable for display in the debugger.
- ///
- private string DebuggerDisplay => $"[{Row}, {Column}]";
- }
-}
diff --git a/src/libraries/Common/src/System/Collections/Generic/SparseArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/SparseArrayBuilder.cs
deleted file mode 100644
index b027b21cdaccf8..00000000000000
--- a/src/libraries/Common/src/System/Collections/Generic/SparseArrayBuilder.cs
+++ /dev/null
@@ -1,224 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-
-namespace System.Collections.Generic
-{
- ///
- /// Represents a reserved region within a .
- ///
- [DebuggerDisplay("{DebuggerDisplay,nq}")]
- internal readonly struct Marker
- {
- ///
- /// Constructs a new marker.
- ///
- /// The number of items to reserve.
- /// The index in the builder where this marker starts.
- public Marker(int count, int index)
- {
- Debug.Assert(count >= 0);
- Debug.Assert(index >= 0);
-
- Count = count;
- Index = index;
- }
-
- ///
- /// The number of items to reserve.
- ///
- public int Count { get; }
-
- ///
- /// The index in the builder where this marker starts.
- ///
- public int Index { get; }
-
- ///
- /// Gets a string suitable for display in the debugger.
- ///
- private string DebuggerDisplay => $"{nameof(Index)} = {Index}, {nameof(Count)} = {Count}";
- }
-
- ///
- /// Helper type for building arrays where sizes of certain segments are known in advance.
- ///
- /// The element type.
- internal struct SparseArrayBuilder
- {
- ///
- /// The underlying builder that stores items from non-reserved regions.
- ///
- ///
- /// This field is a mutable struct; do not mark it readonly.
- ///
- private LargeArrayBuilder _builder;
-
- ///
- /// The list of reserved regions within this builder.
- ///
- ///
- /// This field is a mutable struct; do not mark it readonly.
- ///
- private ArrayBuilder _markers;
-
- ///
- /// The total number of reserved slots within this builder.
- ///
- private int _reservedCount;
-
- ///
- /// Constructs a new builder.
- ///
- public SparseArrayBuilder()
- {
- this = default;
- _builder = new LargeArrayBuilder();
- }
-
- ///
- /// The total number of items in this builder, including reserved regions.
- ///
- public int Count => checked(_builder.Count + _reservedCount);
-
- ///
- /// The list of reserved regions in this builder.
- ///
- public ArrayBuilder Markers => _markers;
-
- ///
- /// Adds an item to this builder.
- ///
- /// The item to add.
- public void Add(T item) => _builder.Add(item);
-
- ///
- /// Adds a range of items to this builder.
- ///
- /// The sequence to add.
- public void AddRange(IEnumerable items) => _builder.AddRange(items);
-
- ///
- /// Copies the contents of this builder to the specified array.
- ///
- /// The destination array.
- /// The index in to start copying to.
- /// The number of items to copy.
- public void CopyTo(T[] array, int arrayIndex, int count)
- {
- Debug.Assert(array != null);
- Debug.Assert(arrayIndex >= 0);
- Debug.Assert(count >= 0 && count <= Count);
- Debug.Assert(array.Length - arrayIndex >= count);
-
- int copied = 0;
- var position = CopyPosition.Start;
-
- for (int i = 0; i < _markers.Count; i++)
- {
- Marker marker = _markers[i];
-
- // During this iteration, copy until we satisfy `count` or reach the marker.
- int toCopy = Math.Min(marker.Index - copied, count);
-
- if (toCopy > 0)
- {
- position = _builder.CopyTo(position, array, arrayIndex, toCopy);
-
- arrayIndex += toCopy;
- copied += toCopy;
- count -= toCopy;
- }
-
- if (count == 0)
- {
- return;
- }
-
- // We hit our marker. Advance until we satisfy `count` or fulfill `marker.Count`.
- int reservedCount = Math.Min(marker.Count, count);
-
- arrayIndex += reservedCount;
- copied += reservedCount;
- count -= reservedCount;
- }
-
- if (count > 0)
- {
- // Finish copying after the final marker.
- _builder.CopyTo(position, array, arrayIndex, count);
- }
- }
-
- ///
- /// Reserves a region starting from the current index.
- ///
- /// The number of items to reserve.
- ///
- /// This method will not make optimizations if
- /// is zero; the caller is responsible for doing so. The reason for this
- /// is that the number of markers needs to match up exactly with the number
- /// of times was called.
- ///
- public void Reserve(int count)
- {
- Debug.Assert(count >= 0);
-
- _markers.Add(new Marker(count: count, index: Count));
-
- checked
- {
- _reservedCount += count;
- }
- }
-
- ///
- /// Reserves a region if the items' count can be predetermined; otherwise, adds the items to this builder.
- ///
- /// The items to reserve or add.
- /// true if the items were reserved; otherwise, false.
- ///
- /// If the items' count is predetermined to be 0, no reservation is made and the return value is false.
- /// The effect is the same as if the items were added, since adding an empty collection does nothing.
- ///
- public bool ReserveOrAdd(IEnumerable items)
- {
- int itemCount;
- if (System.Linq.Enumerable.TryGetNonEnumeratedCount(items, out itemCount))
- {
- if (itemCount > 0)
- {
- Reserve(itemCount);
- return true;
- }
- }
- else
- {
- AddRange(items);
- }
- return false;
- }
-
- ///
- /// Creates an array from the contents of this builder.
- ///
- ///
- /// Regions created with will be default-initialized.
- ///
- public T[] ToArray()
- {
- // If no regions were reserved, there are no 'gaps' we need to add to the array.
- // In that case, we can just call ToArray on the underlying builder.
- if (_markers.Count == 0)
- {
- Debug.Assert(_reservedCount == 0);
- return _builder.ToArray();
- }
-
- var array = new T[Count];
- CopyTo(array, 0, array.Length);
- return array;
- }
- }
-}
diff --git a/src/libraries/Common/tests/Common.Tests.csproj b/src/libraries/Common/tests/Common.Tests.csproj
index eae8a369ceff58..cded1098de094c 100644
--- a/src/libraries/Common/tests/Common.Tests.csproj
+++ b/src/libraries/Common/tests/Common.Tests.csproj
@@ -23,10 +23,6 @@
Link="Common\System\StringExtensions.cs" />
-
-
-
diff --git a/src/libraries/Common/tests/Tests/System/Collections/Generic/LargeArrayBuilderTests.cs b/src/libraries/Common/tests/Tests/System/Collections/Generic/LargeArrayBuilderTests.cs
deleted file mode 100644
index e3008478057cc3..00000000000000
--- a/src/libraries/Common/tests/Tests/System/Collections/Generic/LargeArrayBuilderTests.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Linq;
-using Xunit;
-
-namespace System.Collections.Generic.Tests
-{
- public abstract class LargeArrayBuilderTests where TGenerator : IGenerator, new()
- {
- private static readonly TGenerator s_generator = new TGenerator();
-
- [Fact]
- public void Constructor()
- {
- var builder = new LargeArrayBuilder();
-
- Assert.Equal(0, builder.Count);
- Assert.Same(Array.Empty(), builder.ToArray());
- }
-
- [Theory]
- [MemberData(nameof(EnumerableData))]
- public void AddCountAndToArray(IEnumerable seed)
- {
- var builder1 = new LargeArrayBuilder();
- var builder2 = new LargeArrayBuilder();
-
- int count = 0;
- foreach (T item in seed)
- {
- count++;
-
- builder1.Add(item);
- builder2.SlowAdd(item); // Verify SlowAdd has exactly the same effect as Add.
-
- Assert.Equal(count, builder1.Count);
- Assert.Equal(count, builder2.Count);
-
- Assert.Equal(seed.Take(count), builder1.ToArray());
- Assert.Equal(seed.Take(count), builder2.ToArray());
- }
- }
-
- [Theory]
- [MemberData(nameof(MaxCapacityData))]
- public void MaxCapacity(IEnumerable seed, int maxCapacity)
- {
- var builder = new LargeArrayBuilder(maxCapacity);
-
- for (int i = 0; i < maxCapacity; i++)
- {
- builder.Add(seed.ElementAt(i));
-
- int count = i + 1;
- Assert.Equal(count, builder.Count);
- Assert.Equal(seed.Take(count), builder.ToArray());
- }
- }
-
- [Theory]
- [MemberData(nameof(EnumerableData))]
- public void AddRange(IEnumerable seed)
- {
- var builder = new LargeArrayBuilder();
-
- // Call AddRange multiple times and verify contents w/ each iteration.
- for (int i = 1; i <= 10; i++)
- {
- builder.AddRange(seed);
-
- IEnumerable expected = Enumerable.Repeat(seed, i).SelectMany(s => s);
- Assert.Equal(expected, builder.ToArray());
- }
- }
-
- [Theory]
- [MemberData(nameof(CopyToData))]
- public void CopyTo(IEnumerable seed, int index, int count)
- {
- var array = new T[seed.Count()];
-
- var builder = new LargeArrayBuilder();
- builder.AddRange(seed);
- builder.CopyTo(array, index, count);
-
- // Ensure we don't copy out of bounds by verifying contents outside the copy area, too.
- IEnumerable prefix = array.Take(index);
- IEnumerable suffix = array.Skip(index + count);
- IEnumerable actual = array.Skip(index).Take(count);
-
- Assert.Equal(Enumerable.Repeat(default(T), index), prefix);
- Assert.Equal(Enumerable.Repeat(default(T), array.Length - index - count), suffix);
- Assert.Equal(seed.Take(count), actual);
- }
-
- public static TheoryData> EnumerableData()
- {
- var data = new TheoryData>();
-
- foreach (int count in Counts)
- {
- data.Add(Enumerable.Repeat(default(T), count));
-
- // Test perf: Capture the items into a List here so we
- // only enumerate the sequence once.
- data.Add(s_generator.GenerateEnumerable(count).ToList());
- }
-
- return data;
- }
-
- public static TheoryData, int> MaxCapacityData()
- {
- var data = new TheoryData, int>();
-
- IEnumerable> enumerables = EnumerableData().Select(array => array[0]).Cast>();
-
- foreach (IEnumerable enumerable in enumerables)
- {
- int count = enumerable.Count();
- data.Add(enumerable, count);
- }
-
- return data;
- }
-
- public static TheoryData, int, int> CopyToData()
- {
- var data = new TheoryData, int, int>();
-
- IEnumerable> enumerables = EnumerableData().Select(array => array[0]).Cast>();
-
- foreach (IEnumerable enumerable in enumerables)
- {
- int count = enumerable.Count();
- data.Add(enumerable, 0, count);
-
- // We want to make sure CopyTo works with different indices/counts too.
- data.Add(enumerable, 0, count / 2);
- data.Add(enumerable, count / 2, count / 2);
- data.Add(enumerable, count / 4, count / 2);
- }
-
- return data;
- }
-
- private static IEnumerable Counts
- {
- get
- {
- for (int i = 0; i < 6; i++)
- {
- int powerOfTwo = 1 << i;
-
- // Return numbers of the form 2^N - 1, 2^N and 2^N + 1
- // This should cover most of the interesting cases
- yield return powerOfTwo - 1;
- yield return powerOfTwo;
- yield return powerOfTwo + 1;
- }
- }
- }
- }
-
- public class LargeArrayBuilderTestsInt32 : LargeArrayBuilderTests
- {
- public sealed class Generator : IGenerator
- {
- public int Generate(int seed) => seed;
- }
- }
-
- public class LargeArrayBuilderTestsString : LargeArrayBuilderTests
- {
- public sealed class Generator : IGenerator
- {
- public string Generate(int seed) => seed.ToString();
- }
- }
-}
diff --git a/src/libraries/System.Linq/System.Linq.sln b/src/libraries/System.Linq/System.Linq.sln
index b6b2290abd290c..5f3c4a80c22450 100644
--- a/src/libraries/System.Linq/System.Linq.sln
+++ b/src/libraries/System.Linq/System.Linq.sln
@@ -1,4 +1,8 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34431.11
+MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{AF1B1B01-A4EC-45F4-AE51-CC1FA7892181}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Collections", "..\System.Collections\ref\System.Collections.csproj", "{3A8560D8-0E79-4BDE-802A-C96C7FE98258}"
@@ -35,11 +39,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E6102BFA-080
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{E61195C4-72B4-47A3-AC98-1F896A0C770F}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{8CA90AB2-58B9-45E7-A684-EDB60C6924B0}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8CA90AB2-58B9-45E7-A684-EDB60C6924B0}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{7C5B49B9-F7D9-41FB-A8FA-94328BDDCCD1}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{7C5B49B9-F7D9-41FB-A8FA-94328BDDCCD1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D}"
EndProject
@@ -111,24 +115,28 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{AF1B1B01-A4EC-45F4-AE51-CC1FA7892181} = {E291F4BF-7B8B-45AD-88F5-FB8B8380C126}
- {80A4051B-4A36-4A8B-BA43-A5AB8AA959F3} = {E291F4BF-7B8B-45AD-88F5-FB8B8380C126}
{3A8560D8-0E79-4BDE-802A-C96C7FE98258} = {0BCB262A-FC13-4A48-BB0B-9FA293594701}
{7E4C1F09-B4F2-470E-9E7B-2C386E93D657} = {0BCB262A-FC13-4A48-BB0B-9FA293594701}
- {D3160C37-FC48-4907-8F4A-F584ED12B275} = {0BCB262A-FC13-4A48-BB0B-9FA293594701}
{14B966BB-CE23-4432-ADBB-89974389AC1D} = {E6102BFA-0803-4AB7-8E91-C4D3B42AFA20}
+ {80A4051B-4A36-4A8B-BA43-A5AB8AA959F3} = {E291F4BF-7B8B-45AD-88F5-FB8B8380C126}
{9A13A12F-C924-43AF-94AF-6F1B33582D27} = {E61195C4-72B4-47A3-AC98-1F896A0C770F}
{4BEC631E-B5FD-453F-82A0-C95C461798EA} = {E61195C4-72B4-47A3-AC98-1F896A0C770F}
{C8F0459C-15D5-4624-8CE4-E93ADF96A28C} = {E61195C4-72B4-47A3-AC98-1F896A0C770F}
+ {D3160C37-FC48-4907-8F4A-F584ED12B275} = {0BCB262A-FC13-4A48-BB0B-9FA293594701}
{E0CA3ED5-EE6C-4F7C-BCE7-EFB1D64A9CD1} = {84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E}
{3EFB74E7-616A-48C1-B43B-3F89AA5013E6} = {84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E}
- {84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D}
{28ABC524-ACEE-4183-A64A-49E3DC830595} = {8CA90AB2-58B9-45E7-A684-EDB60C6924B0}
{721DB3D9-8221-424E-BE29-084CDD20D26E} = {8CA90AB2-58B9-45E7-A684-EDB60C6924B0}
- {8CA90AB2-58B9-45E7-A684-EDB60C6924B0} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D}
{E19B8772-2DBD-4274-8190-F3CC0242A1C0} = {7C5B49B9-F7D9-41FB-A8FA-94328BDDCCD1}
+ {84E98F7C-FA2B-4048-AB7C-9FCDEA9CD37E} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D}
+ {8CA90AB2-58B9-45E7-A684-EDB60C6924B0} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D}
{7C5B49B9-F7D9-41FB-A8FA-94328BDDCCD1} = {0ADC596A-5B2E-4E5F-B5B5-DEB65A6C7E9D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A4970D79-BF1C-4343-9070-B409DBB69F93}
EndGlobalSection
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{3efb74e7-616a-48c1-b43b-3f89aa5013e6}*SharedItemsImports = 5
+ ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{721db3d9-8221-424e-be29-084cdd20d26e}*SharedItemsImports = 5
+ EndGlobalSection
EndGlobal
diff --git a/src/libraries/System.Linq/src/System.Linq.csproj b/src/libraries/System.Linq/src/System.Linq.csproj
index 6e810da322b40f..2fc4153be6890a 100644
--- a/src/libraries/System.Linq/src/System.Linq.csproj
+++ b/src/libraries/System.Linq/src/System.Linq.csproj
@@ -14,8 +14,6 @@
-
@@ -36,26 +34,13 @@
-
-
-
-
-
-
-
@@ -88,6 +73,7 @@
+
diff --git a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs
index caf83bee1fcaac..80ee23998603fa 100644
--- a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs
@@ -22,22 +22,45 @@ private sealed partial class AppendPrepend1Iterator
private TSource[] LazyToArray()
{
Debug.Assert(GetCount(onlyIfCheap: true) == -1);
+ TSource[] result;
- LargeArrayBuilder builder = new();
-
- if (!_appending)
+ if (_source is ICollection c)
{
- builder.SlowAdd(_item);
+ // Allocate an array of the exact size needed. We have a collection
+ // with an additional item either before it or after it; copy them
+ // all to the new array appropriately.
+ result = new TSource[c.Count + 1];
+ if (_appending)
+ {
+ c.CopyTo(result, 0);
+ result[^1] = _item;
+ }
+ else
+ {
+ c.CopyTo(result, 1);
+ result[0] = _item;
+ }
}
-
- builder.AddRange(_source);
-
- if (_appending)
+ else
{
- builder.SlowAdd(_item);
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
+ if (_appending)
+ {
+ builder.AddNonICollectionRange(_source);
+ builder.Add(_item);
+ }
+ else
+ {
+ builder.Add(_item);
+ builder.AddNonICollectionRange(_source);
+ }
+
+ result = builder.ToArray();
+ builder.Dispose();
}
- return builder.ToArray();
+ return result;
}
public override TSource[] ToArray()
@@ -60,11 +83,21 @@ public override TSource[] ToArray()
index = 1;
}
- EnumerableHelpers.Copy(_source, array, index, count - 1);
+ if (_source is ICollection collection)
+ {
+ collection.CopyTo(array, index);
+ }
+ else
+ {
+ foreach (TSource item in _source)
+ {
+ array[index++] = item;
+ }
+ }
if (_appending)
{
- array[array.Length - 1] = _item;
+ array[^1] = _item;
}
return array;
@@ -113,26 +146,35 @@ private TSource[] LazyToArray()
{
Debug.Assert(GetCount(onlyIfCheap: true) == -1);
- SparseArrayBuilder builder = new();
-
- if (_prepended != null)
+ if (_source is ICollection c)
{
- builder.Reserve(_prependCount);
- }
+ var result = new TSource[checked(_prependCount + c.Count + _appendCount)];
- builder.AddRange(_source);
+ _prepended?.Fill(result);
+ c.CopyTo(result, _prependCount);
+ _appended?.FillReversed(result);
- if (_appended != null)
- {
- builder.Reserve(_appendCount);
+ return result;
}
+ else
+ {
+ // Create the new builder with the prepended content and source content. Then
+ // build the resulting array with enough space to also hold any appended content,
+ // and write the appended content directly into the resulting array.
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
+ for (SingleLinkedNode? node = _prepended; node is not null; node = node.Linked)
+ {
+ builder.Add(node.Item);
+ }
+ builder.AddNonICollectionRange(_source);
- TSource[] array = builder.ToArray();
-
- _prepended?.Fill(array);
- _appended?.FillReversed(array);
+ TSource[] result = builder.ToArray(_appendCount);
+ builder.Dispose();
- return array;
+ _appended?.FillReversed(result);
+ return result;
+ }
}
public override TSource[] ToArray()
diff --git a/src/libraries/System.Linq/src/System/Linq/Average.cs b/src/libraries/System.Linq/src/System/Linq/Average.cs
index e71b3558d71493..df0f34cf098eb6 100644
--- a/src/libraries/System.Linq/src/System/Linq/Average.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Average.cs
@@ -10,6 +10,11 @@ public static partial class Enumerable
{
public static double Average(this IEnumerable source)
{
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
if (source.TryGetSpan(out ReadOnlySpan span))
{
// Int32 is special-cased separately from the rest of the types as it can be vectorized:
@@ -79,6 +84,11 @@ private static TResult Average(this IEnumerable<
where TAccumulator : struct, INumber
where TResult : struct, INumber
{
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
if (source.TryGetSpan(out ReadOnlySpan span))
{
if (span.IsEmpty)
diff --git a/src/libraries/System.Linq/src/System/Linq/Buffer.cs b/src/libraries/System.Linq/src/System/Linq/Buffer.cs
deleted file mode 100644
index 1821a87b100d52..00000000000000
--- a/src/libraries/System.Linq/src/System/Linq/Buffer.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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;
-
-namespace System.Linq
-{
- ///
- /// A buffer into which the contents of an can be stored.
- ///
- /// The type of the buffer's elements.
- internal readonly struct Buffer
- {
- ///
- /// The stored items.
- ///
- internal readonly TElement[] _items;
-
- ///
- /// The number of stored items.
- ///
- internal readonly int _count;
-
- ///
- /// Fully enumerates the provided enumerable and stores its items into an array.
- ///
- /// The enumerable to be store.
- internal Buffer(IEnumerable source)
- {
- if (source is IIListProvider iterator)
- {
- TElement[] array = iterator.ToArray();
- _items = array;
- _count = array.Length;
- }
- else
- {
- _items = EnumerableHelpers.ToArray(source, out _count);
- }
- }
- }
-}
diff --git a/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs
index 1df7809baa65c6..d3e67aac26e166 100644
--- a/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Concat.SpeedOpt.cs
@@ -38,27 +38,56 @@ public override int GetCount(bool onlyIfCheap)
public override TSource[] ToArray()
{
- SparseArrayBuilder builder = new();
+ ICollection? firstCollection = _first as ICollection;
+ ICollection? secondCollection = _second as ICollection;
- bool reservedFirst = builder.ReserveOrAdd(_first);
- bool reservedSecond = builder.ReserveOrAdd(_second);
+ if (firstCollection is not null && secondCollection is not null)
+ {
+ // Both sources are ICollection, so we know their sizes and can just copy them.
+ int firstCount = firstCollection.Count;
+ TSource[] result = new TSource[checked(firstCount + secondCollection.Count)];
- TSource[] array = builder.ToArray();
+ firstCollection.CopyTo(result, 0);
+ secondCollection.CopyTo(result, firstCount);
- if (reservedFirst)
- {
- Marker marker = builder.Markers.First();
- Debug.Assert(marker.Index == 0);
- EnumerableHelpers.Copy(_first, array, 0, marker.Count);
+ return result;
}
-
- if (reservedSecond)
+ else
{
- Marker marker = builder.Markers.Last();
- EnumerableHelpers.Copy(_second, array, marker.Index, marker.Count);
- }
+ // We don't know the sizes of at least one if not both sources, so we need a builder.
+ // If we don't know the sizes of both, we'll just append each into the builder and
+ // use the builder to create the overall array. If we know the size of one, we'll
+ // only buffer the other.
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
+ TSource[] result;
+
+ if (firstCollection is not null)
+ {
+ int firstCount = firstCollection.Count;
+ builder.AddNonICollectionRange(_second);
+ result = new TSource[checked(firstCount + builder.Count)];
+ firstCollection.CopyTo(result, 0);
+ builder.ToSpan(result.AsSpan(firstCount));
+ }
+ else if (secondCollection is not null)
+ {
+ int secondCount = secondCollection.Count;
+ builder.AddNonICollectionRange(_first);
+ result = new TSource[checked(builder.Count + secondCount)];
+ builder.ToSpan(result);
+ secondCollection.CopyTo(result, result.Length - secondCount);
+ }
+ else
+ {
+ builder.AddNonICollectionRange(_first);
+ builder.AddNonICollectionRange(_second);
+ result = builder.ToArray();
+ }
- return array;
+ builder.Dispose();
+ return result;
+ }
}
}
@@ -100,10 +129,12 @@ public override int GetCount(bool onlyIfCheap)
private TSource[] LazyToArray()
{
+ // All of the sources being ICollection is handled by PreallocatingToArray, so if we're here,
+ // at least one source isn't an ICollection.
Debug.Assert(!_hasOnlyCollections);
- SparseArrayBuilder builder = new();
- ArrayBuilder deferredCopies = default;
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
for (int i = 0; ; i++)
{
@@ -111,30 +142,19 @@ private TSource[] LazyToArray()
// quadratic behavior, because we need to add the sources in order.
// On the bright side, the bottleneck will usually be iterating, buffering, and copying
// each of the enumerables, so this shouldn't be a noticeable perf hit for most scenarios.
-
IEnumerable? source = GetEnumerable(i);
if (source == null)
{
break;
}
- if (builder.ReserveOrAdd(source))
- {
- deferredCopies.Add(i);
- }
+ builder.AddRange(source);
}
- TSource[] array = builder.ToArray();
+ TSource[] result = builder.ToArray();
+ builder.Dispose();
- ArrayBuilder markers = builder.Markers;
- for (int i = 0; i < markers.Count; i++)
- {
- Marker marker = markers[i];
- IEnumerable source = GetEnumerable(deferredCopies[i])!;
- EnumerableHelpers.Copy(source, array, marker.Index, marker.Count);
- }
-
- return array;
+ return result;
}
private TSource[] PreallocatingToArray()
diff --git a/src/libraries/System.Linq/src/System/Linq/Enumerable.cs b/src/libraries/System.Linq/src/System/Linq/Enumerable.cs
index 887a3800683124..fbdf9a12a28b65 100644
--- a/src/libraries/System.Linq/src/System/Linq/Enumerable.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Enumerable.cs
@@ -37,18 +37,8 @@ internal static Span SetCountAndGetSpan(List list, int count)
/// Validates that source is not null and then tries to extract a span from the source.
[MethodImpl(MethodImplOptions.AggressiveInlining)] // fast type checks that don't add a lot of overhead
- private static bool TryGetSpan(this IEnumerable source, out ReadOnlySpan span)
- // This constraint isn't required, but the overheads involved here can be more substantial when TSource
- // is a reference type and generic implementations are shared. So for now we're protecting ourselves
- // and forcing a conscious choice to remove this in the future, at which point it should be paired with
- // sufficient performance testing.
- where TSource : struct
+ internal static bool TryGetSpan(this IEnumerable source, out ReadOnlySpan span)
{
- if (source is null)
- {
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
- }
-
// Use `GetType() == typeof(...)` rather than `is` to avoid cast helpers. This is measurably cheaper
// but does mean we could end up missing some rare cases where we could get a span but don't (e.g. a uint[]
// masquerading as an int[]). That's an acceptable tradeoff. The Unsafe usage is only after we've
diff --git a/src/libraries/System.Linq/src/System/Linq/Max.cs b/src/libraries/System.Linq/src/System/Linq/Max.cs
index d3189720456d73..d0da2f7bfd7415 100644
--- a/src/libraries/System.Linq/src/System/Linq/Max.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Max.cs
@@ -99,6 +99,11 @@ private static T MaxFloat(this IEnumerable source) where T : struct, IFloa
{
T value;
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
if (source.TryGetSpan(out ReadOnlySpan span))
{
if (span.IsEmpty)
@@ -218,6 +223,11 @@ public static decimal Max(this IEnumerable source)
{
decimal value;
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
if (source.TryGetSpan(out ReadOnlySpan span))
{
if (span.IsEmpty)
diff --git a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs
index 759c4ce65627dd..f8af828bc95087 100644
--- a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs
+++ b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs
@@ -25,6 +25,11 @@ private static T MinMaxInteger(this IEnumerable source)
{
T value;
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
if (source.TryGetSpan(out ReadOnlySpan span))
{
if (span.IsEmpty)
diff --git a/src/libraries/System.Linq/src/System/Linq/Min.cs b/src/libraries/System.Linq/src/System/Linq/Min.cs
index 632d643655174a..3a0f2130d966b3 100644
--- a/src/libraries/System.Linq/src/System/Linq/Min.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Min.cs
@@ -81,6 +81,11 @@ private static T MinFloat(this IEnumerable source) where T : struct, IFloa
{
T value;
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
if (source.TryGetSpan(out ReadOnlySpan span))
{
if (span.IsEmpty)
@@ -197,6 +202,11 @@ public static decimal Min(this IEnumerable source)
{
decimal value;
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
if (source.TryGetSpan(out ReadOnlySpan span))
{
if (span.IsEmpty)
diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs
index 1e6f367bb37a85..b7a45d95a1a1fb 100644
--- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs
@@ -11,38 +11,36 @@ internal abstract partial class OrderedEnumerable : IPartition buffer = new Buffer(_source);
-
- int count = buffer._count;
- if (count == 0)
+ TElement[] buffer = _source.ToArray();
+ if (buffer.Length == 0)
{
- return buffer._items;
+ return buffer;
}
- TElement[] array = new TElement[count];
+ TElement[] array = new TElement[buffer.Length];
Fill(buffer, array);
return array;
}
public virtual List ToList()
{
- Buffer buffer = new Buffer(_source);
- int count = buffer._count;
- List list = new List(count);
- if (count > 0)
+ TElement[] buffer = _source.ToArray();
+
+ List list = new();
+ if (buffer.Length > 0)
{
- Fill(buffer, Enumerable.SetCountAndGetSpan(list, count));
+ Fill(buffer, Enumerable.SetCountAndGetSpan(list, buffer.Length));
}
return list;
}
- private void Fill(Buffer buffer, Span destination)
+ private void Fill(TElement[] buffer, Span destination)
{
int[] map = SortedMap(buffer);
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = buffer._items[map[i]];
+ destination[i] = buffer[map[i]];
}
}
@@ -58,21 +56,20 @@ public int GetCount(bool onlyIfCheap)
internal TElement[] ToArray(int minIdx, int maxIdx)
{
- Buffer buffer = new Buffer(_source);
- int count = buffer._count;
- if (count <= minIdx)
+ TElement[] buffer = _source.ToArray();
+ if (buffer.Length <= minIdx)
{
return [];
}
- if (count <= maxIdx)
+ if (buffer.Length <= maxIdx)
{
- maxIdx = count - 1;
+ maxIdx = buffer.Length - 1;
}
if (minIdx == maxIdx)
{
- return [GetEnumerableSorter().ElementAt(buffer._items, count, minIdx)];
+ return [GetEnumerableSorter().ElementAt(buffer, buffer.Length, minIdx)];
}
TElement[] array = new TElement[maxIdx - minIdx + 1];
@@ -84,35 +81,34 @@ internal TElement[] ToArray(int minIdx, int maxIdx)
internal List ToList(int minIdx, int maxIdx)
{
- Buffer buffer = new Buffer(_source);
- int count = buffer._count;
- if (count <= minIdx)
+ TElement[] buffer = _source.ToArray();
+ if (buffer.Length <= minIdx)
{
return new List();
}
- if (count <= maxIdx)
+ if (buffer.Length <= maxIdx)
{
- maxIdx = count - 1;
+ maxIdx = buffer.Length - 1;
}
if (minIdx == maxIdx)
{
- return new List(1) { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) };
+ return new List(1) { GetEnumerableSorter().ElementAt(buffer, buffer.Length, minIdx) };
}
- List list = new List(maxIdx - minIdx + 1);
+ List list = new();
Fill(minIdx, maxIdx, buffer, Enumerable.SetCountAndGetSpan(list, maxIdx - minIdx + 1));
return list;
}
- private void Fill(int minIdx, int maxIdx, Buffer buffer, Span destination)
+ private void Fill(int minIdx, int maxIdx, TElement[] buffer, Span destination)
{
int[] map = SortedMap(buffer, minIdx, maxIdx);
int idx = 0;
while (minIdx <= maxIdx)
{
- destination[idx] = buffer._items[map[minIdx]];
+ destination[idx] = buffer[map[minIdx]];
++idx;
++minIdx;
}
@@ -147,12 +143,11 @@ internal int GetCount(int minIdx, int maxIdx, bool onlyIfCheap)
if (index > 0)
{
- Buffer buffer = new Buffer(_source);
- int count = buffer._count;
- if (index < count)
+ TElement[] buffer = _source.ToArray();
+ if (index < buffer.Length)
{
found = true;
- return GetEnumerableSorter().ElementAt(buffer._items, count, index);
+ return GetEnumerableSorter().ElementAt(buffer, buffer.Length, index);
}
}
@@ -216,29 +211,30 @@ internal int GetCount(int minIdx, int maxIdx, bool onlyIfCheap)
public TElement? TryGetLast(int minIdx, int maxIdx, out bool found)
{
- Buffer buffer = new Buffer(_source);
- int count = buffer._count;
- if (minIdx >= count)
+ TElement[] buffer = _source.ToArray();
+ if (minIdx < buffer.Length)
{
- found = false;
- return default;
+ found = true;
+ return (maxIdx < buffer.Length - 1) ?
+ GetEnumerableSorter().ElementAt(buffer, buffer.Length, maxIdx) :
+ Last(buffer);
}
- found = true;
- return (maxIdx < count - 1) ? GetEnumerableSorter().ElementAt(buffer._items, count, maxIdx) : Last(buffer);
+ found = false;
+ return default;
}
- private TElement Last(Buffer buffer)
+ private TElement Last(TElement[] items)
{
CachingComparer comparer = GetComparer();
- TElement[] items = buffer._items;
- int count = buffer._count;
+
TElement value = items[0];
comparer.SetElement(value);
- for (int i = 1; i != count; ++i)
+
+ for (int i = 1; i < items.Length; ++i)
{
TElement x = items[i];
- if (comparer.Compare(x, false) >= 0)
+ if (comparer.Compare(x, cacheLower: false) >= 0)
{
value = x;
}
diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
index 1bcdb6b2b780d1..2828d765fdfaea 100644
--- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
+++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
@@ -13,28 +13,28 @@ internal abstract partial class OrderedEnumerable : IOrderedEnumerable
protected OrderedEnumerable(IEnumerable source) => _source = source;
- private int[] SortedMap(Buffer buffer) => GetEnumerableSorter().Sort(buffer._items, buffer._count);
+ private int[] SortedMap(TElement[] buffer) => GetEnumerableSorter().Sort(buffer, buffer.Length);
- private int[] SortedMap(Buffer buffer, int minIdx, int maxIdx) =>
- GetEnumerableSorter().Sort(buffer._items, buffer._count, minIdx, maxIdx);
+ private int[] SortedMap(TElement[] buffer, int minIdx, int maxIdx) =>
+ GetEnumerableSorter().Sort(buffer, buffer.Length, minIdx, maxIdx);
public virtual IEnumerator GetEnumerator()
{
- Buffer buffer = new Buffer(_source);
- if (buffer._count > 0)
+ TElement[] buffer = _source.ToArray();
+ if (buffer.Length > 0)
{
int[] map = SortedMap(buffer);
- for (int i = 0; i < buffer._count; i++)
+ for (int i = 0; i < buffer.Length; i++)
{
- yield return buffer._items[map[i]];
+ yield return buffer[map[i]];
}
}
}
internal IEnumerator GetEnumerator(int minIdx, int maxIdx)
{
- Buffer buffer = new Buffer(_source);
- int count = buffer._count;
+ TElement[] buffer = _source.ToArray();
+ int count = buffer.Length;
if (count > minIdx)
{
if (count <= maxIdx)
@@ -44,14 +44,14 @@ internal IEnumerator GetEnumerator(int minIdx, int maxIdx)
if (minIdx == maxIdx)
{
- yield return GetEnumerableSorter().ElementAt(buffer._items, count, minIdx);
+ yield return GetEnumerableSorter().ElementAt(buffer, count, minIdx);
}
else
{
int[] map = SortedMap(buffer, minIdx, maxIdx);
while (minIdx <= maxIdx)
{
- yield return buffer._items[map[minIdx]];
+ yield return buffer[map[minIdx]];
++minIdx;
}
}
@@ -184,13 +184,13 @@ internal override EnumerableSorter GetEnumerableSorter(EnumerableSorte
public override IEnumerator GetEnumerator()
{
- var buffer = new Buffer(_source);
- if (buffer._count > 0)
+ TElement[] buffer = _source.ToArray();
+ if (buffer.Length > 0)
{
- Sort(buffer._items.AsSpan(0, buffer._count), _descending);
- for (int i = 0; i < buffer._count; i++)
+ Sort(buffer, _descending);
+ for (int i = 0; i < buffer.Length; i++)
{
- yield return buffer._items[i];
+ yield return buffer[i];
}
}
}
diff --git a/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs
index dbcd20974a3066..92f71cb058d478 100644
--- a/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs
@@ -511,9 +511,8 @@ public TSource[] ToArray()
int remaining = Limit - 1; // Max number of items left, not counting the current element.
int comparand = HasLimit ? 0 : int.MinValue; // If we don't have an upper bound, have the comparison always return true.
- int maxCapacity = HasLimit ? Limit : int.MaxValue;
- var builder = new LargeArrayBuilder(maxCapacity);
-
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
do
{
remaining--;
@@ -521,7 +520,10 @@ public TSource[] ToArray()
}
while (remaining >= comparand && en.MoveNext());
- return builder.ToArray();
+ TSource[] result = builder.ToArray();
+ builder.Dispose();
+
+ return result;
}
}
diff --git a/src/libraries/System.Linq/src/System/Linq/Reverse.cs b/src/libraries/System.Linq/src/System/Linq/Reverse.cs
index 674b30bbc49c94..2a02115daca12a 100644
--- a/src/libraries/System.Linq/src/System/Linq/Reverse.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Reverse.cs
@@ -59,9 +59,9 @@ public override bool MoveNext()
// Iteration has just started. Capture the source into an array and set _state to 2 + the count.
// Having an extra field for the count would be more readable, but we save it into _state with a
// bias instead to minimize field size of the iterator.
- Buffer buffer = new Buffer(_source);
- _buffer = buffer._items;
- _state = buffer._count + 2;
+ TSource[] buffer = _source.ToArray();
+ _buffer = buffer;
+ _state = buffer.Length + 2;
goto default;
default:
// At this stage, _state starts from 2 + the count. _state - 3 represents the current index into the
diff --git a/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs b/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs
new file mode 100644
index 00000000000000..ef85efa11ee48d
--- /dev/null
+++ b/src/libraries/System.Linq/src/System/Linq/SegmentedArrayBuilder.cs
@@ -0,0 +1,313 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ /// Provides a helper for efficiently building arrays and lists.
+ /// This is implemented as an inline array of rented arrays.
+ /// Specifies the element type of the collection being built.
+ internal ref struct SegmentedArrayBuilder
+ {
+ /// The size to use for the first segment that's stack allocated by the caller.
+ ///
+ /// This value needs to be small enough that we don't need to be overly concerned about really large
+ /// value types. It's not unreasonable for a method to say it has 8 locals of a T, and that's effectively
+ /// what this is.
+ ///
+ private const int ScratchBufferSize = 8;
+ /// Minimum size to request renting from the pool.
+ private const int MinimumRentSize = 16;
+
+ /// The array of segments.
+ /// is how many of the segments are valid in , not including .
+ private Arrays _segments;
+ /// The scratch buffer provided by the caller.
+ /// This is treated as the initial segment, before anything in .
+ private Span _firstSegment;
+ /// The current span. This points either to or to [ - 1].
+ private Span _currentSegment;
+ /// The count of segments in that are valid.
+ /// All but the last are known to be fully populated.
+ private int _segmentsCount;
+ /// The total number of elements in all but the current/last segment.
+ private int _countInFinishedSegments;
+ /// The number of elements in the current/last segment.
+ private int _countInCurrentSegment;
+
+ /// Initialize the builder.
+ /// A buffer that can be used as part of the builder.
+ public SegmentedArrayBuilder(Span scratchBuffer)
+ {
+ _currentSegment = _firstSegment = scratchBuffer;
+ }
+
+ /// Clean up the resources used by the builder.
+ public void Dispose()
+ {
+ int segmentsCount = _segmentsCount;
+ if (segmentsCount != 0)
+ {
+ ReadOnlySpan segments = _segments;
+
+ // We need to return all rented arrays to the pool, and if the arrays contain any references,
+ // we want to clear them first so that the pool doesn't artificially root contained objects.
+ if (RuntimeHelpers.IsReferenceOrContainsReferences())
+ {
+ // Return all but the last segment. All of these are full and need to be entirely cleared.
+ foreach (T[] segment in segments.Slice(0, segmentsCount - 1))
+ {
+ ArrayPool.Shared.Return(segment, clearArray: true);
+ }
+
+ // For the last segment, we can clear only what we know was used.
+ T[] currentSegment = segments[segmentsCount - 1];
+ Array.Clear(currentSegment, 0, _countInCurrentSegment);
+ ArrayPool.Shared.Return(currentSegment);
+ }
+ else
+ {
+ // Return every rented array without clearing.
+ foreach (T[] segment in segments.Slice(0, segmentsCount))
+ {
+ ArrayPool.Shared.Return(segment);
+ }
+ }
+ }
+ }
+
+ /// Gets the number of elements in the builder.
+ public readonly int Count => checked(_countInFinishedSegments + _countInCurrentSegment);
+
+ /// Adds an item into the builder.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(T item)
+ {
+ Span currentSegment = _currentSegment;
+ int countInCurrentSegment = _countInCurrentSegment;
+ if ((uint)countInCurrentSegment < (uint)currentSegment.Length)
+ {
+ currentSegment[countInCurrentSegment] = item;
+ _countInCurrentSegment++;
+ }
+ else
+ {
+ AddSlow(item);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void AddSlow(T item)
+ {
+ Expand();
+ _currentSegment[0] = item;
+ _countInCurrentSegment = 1;
+ }
+
+ /// Adds a collection of items into the builder.
+ public void AddRange(IEnumerable source)
+ {
+ if (source is ICollection collection)
+ {
+ int collectionCount = collection.Count;
+
+ // If the source is empty, there's nothing to add.
+ if (collectionCount == 0)
+ {
+ return;
+ }
+
+ // If the source is something from which we can get a span, e.g. a T[] or a List,
+ // we can do one or two copies to handle copying everything, even if we need to split
+ // across segments.
+ if (Enumerable.TryGetSpan(source, out ReadOnlySpan sourceSpan))
+ {
+ int availableSpaceInCurrentSpan = _currentSegment.Length - _countInCurrentSegment;
+ ReadOnlySpan sourceSlice = sourceSpan.Slice(0, Math.Min(availableSpaceInCurrentSpan, sourceSpan.Length));
+ sourceSlice.CopyTo(_currentSegment.Slice(_countInCurrentSegment));
+ _countInCurrentSegment += sourceSlice.Length;
+ sourceSlice = sourceSpan.Slice(sourceSlice.Length);
+
+ if (!sourceSlice.IsEmpty)
+ {
+ Expand(sourceSlice.Length);
+ sourceSlice.CopyTo(_currentSegment);
+ _countInCurrentSegment = sourceSlice.Length;
+ }
+
+ return;
+ }
+
+ // Otherwise, since we have an ICollection, we'd like to use ICollection.CopyTo, but it
+ // requires targeting an array, so we can't use it if we're using a scratch buffer.
+ bool currentSegmentIsScratchBufferWithRemainingSpace = _segmentsCount == 0 && _countInCurrentSegment < _currentSegment.Length;
+ if (!currentSegmentIsScratchBufferWithRemainingSpace)
+ {
+ // It also only works if we can copy the whole collection in one go, which
+ // means we need enough space in the current segment to hold the whole collection
+ // or we need to be at the end of the current segment so we can allocate a
+ // new one without leaving any holes.
+ int remainingSpaceInCurrentSegment = _currentSegment.Length - _countInCurrentSegment;
+
+ // If there's no space remaining in the current segment, we can just expand
+ // into a new segment that we ensure is large enough.
+ if (remainingSpaceInCurrentSegment == 0)
+ {
+ Expand(collectionCount);
+ collection.CopyTo(_segments[_segmentsCount - 1], 0);
+ _countInCurrentSegment = collectionCount;
+ return;
+ }
+
+ // If there's enough space remaining in the current segment, we can also just copy into it.
+ if (collectionCount <= remainingSpaceInCurrentSegment)
+ {
+ collection.CopyTo(_segments[_segmentsCount - 1], _countInCurrentSegment);
+ _countInCurrentSegment += collectionCount;
+ return;
+ }
+
+ // Otherwise, we're forced to fall back to enumeration.
+ }
+ }
+
+ // Fall back to enumerating and adding each element individually.
+ AddNonICollectionRangeInlined(source);
+ }
+
+ /// Adds a collection of items into the builder.
+ ///
+ /// The implementation assumes the caller has already ruled out the source being
+ /// and ICollection and thus doesn't bother checking to see if it is.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public void AddNonICollectionRange(IEnumerable source) =>
+ AddNonICollectionRangeInlined(source);
+
+ /// Adds a collection of items into the builder.
+ ///
+ /// The implementation assumes the caller has already ruled out the source being
+ /// and ICollection and thus doesn't bother checking to see if it is.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void AddNonICollectionRangeInlined(IEnumerable source)
+ {
+ Span currentSegment = _currentSegment;
+ int countInCurrentSegment = _countInCurrentSegment;
+
+ foreach (T item in source)
+ {
+ if ((uint)countInCurrentSegment < (uint)currentSegment.Length)
+ {
+ currentSegment[countInCurrentSegment] = item;
+ countInCurrentSegment++;
+ }
+ else
+ {
+ Expand();
+ currentSegment = _currentSegment;
+ currentSegment[0] = item;
+ countInCurrentSegment = 1;
+ }
+ }
+
+ _countInCurrentSegment = countInCurrentSegment;
+ }
+
+ /// Creates an array containing all of the elements in the builder.
+ /// The number of extra elements of room to allocate in the resulting array.
+ public T[] ToArray(int additionalLength = 0)
+ {
+ T[] result = [];
+
+ int count = checked(Count + additionalLength);
+ if (count != 0)
+ {
+ result = GC.AllocateUninitializedArray(count);
+ ToSpan(result);
+ }
+
+ return result;
+ }
+
+ /// Populates the destination span with all of the elements in the builder.
+ /// The destination span.
+ public void ToSpan(Span destination)
+ {
+ int segmentsCount = _segmentsCount;
+ if (segmentsCount != 0)
+ {
+ // Copy the first segment
+ ReadOnlySpan firstSegment = _firstSegment;
+ firstSegment.CopyTo(destination);
+ destination = destination.Slice(firstSegment.Length);
+
+ // Copy the 0..N-1 segments
+ segmentsCount--;
+ foreach (T[] arr in ((ReadOnlySpan)_segments).Slice(0, segmentsCount))
+ {
+ ReadOnlySpan segment = arr;
+ segment.CopyTo(destination);
+ destination = destination.Slice(segment.Length);
+ }
+ }
+
+ // Copy the last segment
+ _currentSegment.Slice(0, _countInCurrentSegment).CopyTo(destination);
+ }
+
+ /// Appends a new segment onto the builder.
+ /// The minimum amount of space to allocate in a new segment being appended.
+ private void Expand(int minimumRequired = MinimumRentSize)
+ {
+ if (minimumRequired < MinimumRentSize)
+ {
+ minimumRequired = MinimumRentSize;
+ }
+
+ // Update our count of the number of elements in the arrays.
+ // If we know we're exceeding the maximum allowed array length, throw.
+ int currentSegmentLength = _currentSegment.Length;
+ checked { _countInFinishedSegments += currentSegmentLength; }
+ if (_countInFinishedSegments > Array.MaxLength)
+ {
+ throw new OutOfMemoryException();
+ }
+
+ // Use a typical doubling algorithm to decide the length of the next array
+ // and allocate it. We want to double the current array length, but if the
+ // minimum required is larger than that, use the minimum required. And if
+ // doubling would result in going above the max array length, only use the
+ // max array length, as List does.
+ int newSegmentLength = (int)Math.Min(Math.Max(minimumRequired, currentSegmentLength * 2L), Array.MaxLength);
+ _currentSegment = _segments[_segmentsCount] = ArrayPool.Shared.Rent(newSegmentLength);
+ _segmentsCount++;
+ }
+
+#pragma warning disable IDE0044 // Add readonly modifier
+#pragma warning disable IDE0051 // Remove unused private members
+ /// A struct to hold all of the T[]s that compose the full result set.
+ ///
+ /// Starting at the minimum size of , and with a minimum of doubling
+ /// on every growth, this is large enough to hold the maximum number arrays that could result
+ /// until the total length has exceeded Array.MaxLength.
+ ///
+ [InlineArray(27)]
+ private struct Arrays
+ {
+ private T[] _values;
+ }
+
+ /// Provides a stack-allocatable buffer for use as an argument to the builder.
+ [InlineArray(ScratchBufferSize)]
+ public struct ScratchBuffer
+ {
+ private T _item;
+ }
+#pragma warning restore IDE0051
+#pragma warning restore IDE0044
+ }
+}
diff --git a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs
index c9e8be3f9744f4..f59b6e7b08fbae 100644
--- a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs
@@ -20,23 +20,29 @@ private sealed partial class SelectEnumerableIterator : IIList
{
public TResult[] ToArray()
{
- LargeArrayBuilder builder = new();
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
+ Func selector = _selector;
foreach (TSource item in _source)
{
- builder.Add(_selector(item));
+ builder.Add(selector(item));
}
- return builder.ToArray();
+ TResult[] result = builder.ToArray();
+ builder.Dispose();
+
+ return result;
}
public List ToList()
{
var list = new List();
+ Func selector = _selector;
foreach (TSource item in _source)
{
- list.Add(_selector(item));
+ list.Add(selector(item));
}
return list;
@@ -598,13 +604,19 @@ private TResult[] LazyToArray()
{
Debug.Assert(_source.GetCount(onlyIfCheap: true) == -1);
- LargeArrayBuilder builder = new();
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
+ Func selector = _selector;
foreach (TSource input in _source)
{
- builder.Add(_selector(input));
+ builder.Add(selector(input));
}
- return builder.ToArray();
+
+ TResult[] result = builder.ToArray();
+ builder.Dispose();
+
+ return result;
}
private TResult[] PreallocatingToArray(int count)
diff --git a/src/libraries/System.Linq/src/System/Linq/SelectMany.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/SelectMany.SpeedOpt.cs
index b485700db4dd2c..050ae6a4e06b61 100644
--- a/src/libraries/System.Linq/src/System/Linq/SelectMany.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/SelectMany.SpeedOpt.cs
@@ -31,39 +31,29 @@ public int GetCount(bool onlyIfCheap)
public TResult[] ToArray()
{
- SparseArrayBuilder builder = new();
- ArrayBuilder> deferredCopies = default;
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
- foreach (TSource element in _source)
+ Func> selector = _selector;
+ foreach (TSource item in _source)
{
- IEnumerable enumerable = _selector(element);
-
- if (builder.ReserveOrAdd(enumerable))
- {
- deferredCopies.Add(enumerable);
- }
+ builder.AddRange(selector(item));
}
- TResult[] array = builder.ToArray();
-
- ArrayBuilder markers = builder.Markers;
- for (int i = 0; i < markers.Count; i++)
- {
- Marker marker = markers[i];
- IEnumerable enumerable = deferredCopies[i];
- EnumerableHelpers.Copy(enumerable, array, marker.Index, marker.Count);
- }
+ TResult[] result = builder.ToArray();
+ builder.Dispose();
- return array;
+ return result;
}
public List ToList()
{
var list = new List();
+ Func> selector = _selector;
foreach (TSource element in _source)
{
- list.AddRange(_selector(element));
+ list.AddRange(selector(element));
}
return list;
diff --git a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs
index 051ebe769c1f25..e0bf5e54c51ee0 100644
--- a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs
+++ b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs
@@ -99,7 +99,7 @@ public TSource[] ToArray(int count)
///
/// Fills a start of a span with the items of this node's singly-linked list.
///
- /// The span to fill. Must be the precise size required.
+ /// The span to fill. Must be at least the size required.
public void Fill(Span span)
{
int index = 0;
diff --git a/src/libraries/System.Linq/src/System/Linq/Sum.cs b/src/libraries/System.Linq/src/System/Linq/Sum.cs
index ae670ff3e96876..7eb2dc855dc67b 100644
--- a/src/libraries/System.Linq/src/System/Linq/Sum.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Sum.cs
@@ -25,6 +25,11 @@ private static TResult Sum(this IEnumerable source)
where TSource : struct, INumber
where TResult : struct, INumber
{
+ if (source is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ }
+
if (source.TryGetSpan(out ReadOnlySpan span))
{
return Sum(span);
diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs
index 2fd7f475fafbf4..8b2ad651d1d2bd 100644
--- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs
+++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs
@@ -11,24 +11,54 @@ public static partial class Enumerable
{
public static TSource[] ToArray(this IEnumerable source)
{
- if (source == null)
+ if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
- return source is IIListProvider arrayProvider
- ? arrayProvider.ToArray()
- : EnumerableHelpers.ToArray(source);
+ if (source is IIListProvider arrayProvider)
+ {
+ return arrayProvider.ToArray();
+ }
+
+ if (source is ICollection collection)
+ {
+ int count = collection.Count;
+ if (count != 0)
+ {
+ var result = new TSource[count];
+ collection.CopyTo(result, 0);
+ return result;
+ }
+
+ return [];
+ }
+ else
+ {
+ SegmentedArrayBuilder.ScratchBuffer scratch = default;
+ SegmentedArrayBuilder builder = new(scratch);
+
+ builder.AddNonICollectionRange(source);
+ TSource[] result = builder.ToArray();
+
+ builder.Dispose();
+ return result;
+ }
}
public static List ToList(this IEnumerable source)
{
- if (source == null)
+ if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
- return source is IIListProvider listProvider ? listProvider.ToList() : new List(source);
+ if (source is IIListProvider listProvider)
+ {
+ return listProvider.ToList();
+ }
+
+ return new List(source);
}
///
@@ -99,12 +129,12 @@ public static Dictionary ToDictionary(this IEnumer
public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) where TKey : notnull
{
- if (source == null)
+ if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
- if (keySelector == null)
+ if (keySelector is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector);
}
@@ -152,17 +182,17 @@ public static Dictionary ToDictionary(t
public static Dictionary ToDictionary(this IEnumerable