diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SizeOpt.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SizeOpt.cs
new file mode 100644
index 00000000000000..b832ef0cb9cf8d
--- /dev/null
+++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SizeOpt.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Linq;
+
+namespace System.Collections.Generic
+{
+ ///
+ /// Internal helper functions for working with enumerables.
+ ///
+ internal static partial class EnumerableHelpers
+ {
+ /// 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;
+ }
+
+ var builder = new LargeArrayBuilder(initialize: true);
+ builder.AddRange(source);
+ return builder.ToArray();
+ }
+ }
+}
diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SpeedOpt.cs
new file mode 100644
index 00000000000000..8c64554389ba6e
--- /dev/null
+++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.SpeedOpt.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Linq;
+
+namespace System.Collections.Generic
+{
+ ///
+ /// Internal helper functions for working with enumerables.
+ ///
+ internal static partial class EnumerableHelpers
+ {
+ /// Converts an enumerable to an array.
+ /// The enumerable to convert.
+ /// The resulting array.
+ internal static T[] ToArray(IEnumerable source) => ToArrayEnumerable.Instance.ToArray(source);
+ }
+}
diff --git a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs
index ea39f90da410fc..691f64e2359548 100644
--- a/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs
+++ b/src/libraries/Common/src/System/Collections/Generic/EnumerableHelpers.Linq.cs
@@ -86,30 +86,5 @@ internal static void IterativeCopy(IEnumerable source, T[] array, int arra
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;
- }
-
- var builder = new LargeArrayBuilder(initialize: true);
- builder.AddRange(source);
- return builder.ToArray();
- }
}
}
diff --git a/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs
new file mode 100644
index 00000000000000..b8d1865d1ebd7f
--- /dev/null
+++ b/src/libraries/Common/src/System/Collections/Generic/ToArray.SpeedOpt.cs
@@ -0,0 +1,1021 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+
+namespace System.Collections.Generic
+{
+ internal abstract class ToArrayBase
+ {
+ // from https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element
+ private const int maxByteElementsArraySize = 0X7FFFFFC7;
+ private const int maxOtherElementsArraySize = 0X7FEFFFFF;
+
+ protected const int maxArraySize = maxByteElementsArraySize;
+
+ private const int initialRecursiveDepth = 16;
+ private const int itemsPerRecursion = 4;
+
+ protected const int maxSizeForNoAllocations = initialRecursiveDepth * itemsPerRecursion;
+
+ private int GetBufferSize(int count, int sourceSize)
+ {
+ if (count >= maxByteElementsArraySize)
+ {
+ // We fail here "early" as we could be creating more buffers (which would be smaller thaat the max)
+ // but would eventually lead to an excessive allocation which would throw an OutOfMemory exception
+ throw new OutOfMemoryException();
+ }
+
+ var maxSize =
+ count < sourceSize
+ ? sourceSize
+ : count < maxOtherElementsArraySize
+ ? maxOtherElementsArraySize
+ : maxByteElementsArraySize;
+
+ return (int)Math.Min((uint)maxSize, (uint)count * 2) - count;
+ }
+
+ protected T[] FinishViaAllocations(object source, int sourceSize, ref int sourceIdx, object? predicate, object? selector, int count)
+ {
+ T[] result;
+ int bufferSize = GetBufferSize(count, sourceSize);
+ T[] buffer = new T[bufferSize];
+
+ var (index, moveNext) = PopulateBuffer(buffer, source, ref sourceIdx, predicate, selector);
+
+ if (moveNext)
+ {
+ result = FinishViaAllocations(source, sourceSize, ref sourceIdx, predicate, selector, count + index);
+ }
+ else
+ {
+ result = new T[count + index];
+ }
+
+ Array.Copy(buffer, 0, result, count, index);
+
+ return result;
+ }
+
+ protected abstract (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector);
+
+ protected static T[] Allocate(int count)
+ {
+ if (count == 0)
+ return Array.Empty();
+
+ return new T[count];
+ }
+
+ protected static T[] AllocateAndAssign(int count, T item1)
+ {
+ T[] result = new T[count];
+
+ result[--count] = item1;
+
+ return result;
+ }
+
+ protected static T[] AllocateAndAssign(int count, T item1, T item2)
+ {
+ T[] result = new T[count];
+
+ result[--count] = item2;
+ result[--count] = item1;
+
+ return result;
+ }
+
+ protected static T[] AllocateAndAssign(int count, T item1, T item2, T item3)
+ {
+ T[] result = new T[count];
+
+ result[--count] = item3;
+ result[--count] = item2;
+ result[--count] = item1;
+
+ return result;
+ }
+
+ protected static T[] Assign(T[] result, ref (T, T, T, T) items, int count)
+ {
+ result[--count] = items.Item4;
+ result[--count] = items.Item3;
+ result[--count] = items.Item2;
+ result[--count] = items.Item1;
+
+ return result;
+ }
+ }
+
+ internal class ToArrayEnumerable : ToArrayBase
+ {
+ public static readonly ToArrayEnumerable Instance = new ToArrayEnumerable();
+
+ private ToArrayEnumerable() { }
+
+ public 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;
+ }
+
+ using IEnumerator e = source.GetEnumerator();
+ return InitiallyTryWithNoAllocations(e, 0);
+ }
+
+ public T[] ToArray(IEnumerable source, Func predicate)
+ {
+ Debug.Assert(source != null);
+ using IEnumerator e = source.GetEnumerator();
+ return InitiallyTryWithNoAllocations(e, predicate, 0);
+ }
+
+ private T[] InitiallyTryWithNoAllocations(IEnumerator source, int count)
+ {
+ (T, T, T, T) items;
+
+ if (!source.MoveNext())
+ {
+ return Allocate(count);
+ }
+ items.Item1 = source.Current;
+
+ ++count;
+
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1);
+ }
+ items.Item2 = source.Current;
+
+ ++count;
+
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2);
+ }
+ items.Item3 = source.Current;
+
+ ++count;
+
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3);
+ }
+ items.Item4 = source.Current;
+
+ ++count;
+
+ T[] result;
+ if (count >= maxSizeForNoAllocations)
+ {
+ if (source.MoveNext())
+ result = FinishViaAllocations(source, count);
+ else
+ result = Allocate(count);
+ }
+ else
+ {
+ result = InitiallyTryWithNoAllocations(source, count);
+ }
+
+ return Assign(result, ref items, count);
+
+ T[] FinishViaAllocations(IEnumerator source, int count)
+ {
+ int dummyIdx = 0;
+ return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, null, null, count);
+ }
+ }
+
+ private T[] InitiallyTryWithNoAllocations(IEnumerator source, Func predicate, int count)
+ {
+ (T, T, T, T) items;
+
+ do
+ {
+ if (!source.MoveNext())
+ {
+ return Allocate(count);
+ }
+ items.Item1 = source.Current;
+ }
+ while (!predicate(items.Item1));
+
+ ++count;
+
+ do
+ {
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1);
+ }
+ items.Item2 = source.Current;
+ } while (!predicate(items.Item2));
+
+ ++count;
+
+ do
+ {
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2);
+ }
+ items.Item3 = source.Current;
+ }
+ while (!predicate(items.Item3));
+
+ ++count;
+
+ do
+ {
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3);
+ }
+ items.Item4 = source.Current;
+ }
+ while (!predicate(items.Item4));
+
+ ++count;
+
+ T[] result;
+ if (count >= maxSizeForNoAllocations)
+ {
+ if (source.MoveNext())
+ result = FinishViaAllocations(source, predicate, count);
+ else
+ result = Allocate(count);
+ }
+ else
+ {
+ result = InitiallyTryWithNoAllocations(source, predicate, count);
+ }
+
+ return Assign(result, ref items, count);
+
+ T[] FinishViaAllocations(IEnumerator source, Func? predicate, int count)
+ {
+ int dummyIdx = 0;
+ return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, predicate, null, count);
+ }
+ }
+
+ protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector)
+ {
+ var enumerator = (IEnumerator)source;
+
+ Func typedPredicate = (Func)predicate!;
+
+ return predicate == null ? PopulateBuffer(buffer, enumerator) : PopulateBuffer(buffer, enumerator, typedPredicate);
+ }
+
+ private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e)
+ {
+ bool moveNext;
+ int index;
+
+ for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext(), ++index)
+ {
+ buffer[index] = e.Current;
+ }
+
+ return (index, moveNext);
+ }
+
+ private static (int, bool) PopulateBuffer(T[] buffer, IEnumerator e, Func predicate)
+ {
+ bool moveNext;
+ int index;
+
+ for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext())
+ {
+ var item = e.Current;
+
+ if (!predicate(item))
+ continue;
+
+ buffer[index++] = item;
+ }
+
+ return (index, moveNext);
+ }
+ }
+
+ internal class ToArrayEnumerableWithSelect : ToArrayBase
+ {
+ public static readonly ToArrayEnumerableWithSelect Instance = new ToArrayEnumerableWithSelect();
+
+ private ToArrayEnumerableWithSelect() { }
+
+ public U[] ToArray(IEnumerable source, Func selector)
+ {
+ Debug.Assert(source != null);
+ using IEnumerator e = source.GetEnumerator();
+
+ if (source is ICollection collection)
+ {
+ int count = collection.Count;
+ if (count == 0)
+ {
+ return Array.Empty();
+ }
+
+ var result = new U[count];
+
+ int idx = 0;
+ bool moveNext;
+ for (moveNext = e.MoveNext(), idx = 0; moveNext && idx < result.Length; moveNext = e.MoveNext(), ++idx)
+ {
+ result[idx] = selector(e.Current);
+ }
+ if (moveNext || idx != result.Length)
+ throw new IndexOutOfRangeException();
+
+ return result;
+ }
+
+ return InitiallyTryWithNoAllocations(e, selector, 0);
+ }
+
+ public U[] ToArray(IEnumerable source, Func predicate, Func selector)
+ {
+ Debug.Assert(source != null);
+ using IEnumerator e = source.GetEnumerator();
+ return InitiallyTryWithNoAllocations(e, predicate, selector, 0);
+ }
+
+ private U[] InitiallyTryWithNoAllocations(IEnumerator source, Func selector, int count)
+ {
+ (U, U, U, U) items;
+
+ if (!source.MoveNext())
+ {
+ return Allocate(count);
+ }
+ items.Item1 = selector(source.Current);
+
+ ++count;
+
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1);
+ }
+ items.Item2 = selector(source.Current);
+
+ ++count;
+
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2);
+ }
+ items.Item3 = selector(source.Current);
+
+ ++count;
+
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3);
+ }
+ items.Item4 = selector(source.Current);
+
+ ++count;
+
+ U[] result;
+ if (count >= maxSizeForNoAllocations)
+ {
+ if (source.MoveNext())
+ result = FinishViaAllocations(source, selector, count);
+ else
+ result = Allocate(count);
+ }
+ else
+ {
+ result = InitiallyTryWithNoAllocations(source, selector, count);
+ }
+
+ return Assign(result, ref items, count);
+
+ U[] FinishViaAllocations(IEnumerator source, Func selector, int count)
+ {
+ int dummyIdx = 0;
+ return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, null, selector, count);
+ }
+ }
+
+ private U[] InitiallyTryWithNoAllocations(IEnumerator source, Func predicate, Func selector, int count)
+ {
+ T current;
+ (U, U, U, U) items;
+
+ do
+ {
+ if (!source.MoveNext())
+ {
+ return Allocate(count);
+ }
+ current = source.Current;
+ }
+ while (!predicate(current));
+
+ items.Item1 = selector(current);
+ ++count;
+
+ do
+ {
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1);
+ }
+ current = source.Current;
+ } while (!predicate(current));
+
+ items.Item2 = selector(current);
+ ++count;
+
+ do
+ {
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2);
+ }
+ current = source.Current;
+ }
+ while (!predicate(current));
+
+ items.Item3 = selector(current);
+ ++count;
+
+ do
+ {
+ if (!source.MoveNext())
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3);
+ }
+ current = source.Current;
+ }
+ while (!predicate(current));
+
+ items.Item4 = selector(current);
+ ++count;
+
+ U[] result;
+ if (count >= maxSizeForNoAllocations)
+ {
+ if (source.MoveNext())
+ result = FinishViaAllocations(source, predicate, selector, count);
+ else
+ result = Allocate(count);
+ }
+ else
+ {
+ result = InitiallyTryWithNoAllocations(source, predicate, selector, count);
+ }
+
+ return Assign(result, ref items, count);
+
+ U[] FinishViaAllocations(IEnumerator source, Func predicate, Func selector, int count)
+ {
+ int dummyIdx = 0;
+ return base.FinishViaAllocations(source, maxArraySize, ref dummyIdx, predicate, selector, count);
+ }
+ }
+
+ protected override (int, bool) PopulateBuffer(U[] buffer, object source, ref int sourceIdx, object? predicate, object? selector)
+ {
+ var enumerator = (IEnumerator)source;
+
+ Func typedPredicate = (Func)predicate!;
+ Func typedSelector = (Func)selector!;
+
+ return predicate == null ? PopulateBuffer(buffer, enumerator, typedSelector) : PopulateBuffer(buffer, enumerator, typedPredicate, typedSelector);
+ }
+
+ private static (int, bool) PopulateBuffer(U[] buffer, IEnumerator e, Func selector)
+ {
+ bool moveNext;
+ int index;
+
+ for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext(), ++index)
+ {
+ buffer[index] = selector(e.Current);
+ }
+
+ return (index, moveNext);
+ }
+
+ private static (int, bool) PopulateBuffer(U[] buffer, IEnumerator e, Func predicate, Func selector)
+ {
+ bool moveNext;
+ int index;
+
+ for (moveNext = true, index = 0; moveNext && index < buffer.Length; moveNext = e.MoveNext())
+ {
+ var item = e.Current;
+
+ if (!predicate(item))
+ continue;
+
+ buffer[index++] = selector(item);
+ }
+
+ return (index, moveNext);
+ }
+ }
+
+ internal class ToArrayArrayWhere : ToArrayBase
+ {
+ public static readonly ToArrayArrayWhere Instance = new ToArrayArrayWhere();
+
+ private ToArrayArrayWhere() { }
+
+ public T[] ToArray(T[] source, Func predicate)
+ {
+ Debug.Assert(source != null);
+
+ return InitiallyTryWithNoAllocations(source, 0, predicate, 0);
+ }
+
+ private T[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func predicate, int count)
+ {
+ (T, T, T, T) items;
+
+ do
+ {
+ if (sourceIdx >= source.Length)
+ {
+ return Allocate(count);
+ }
+
+ items.Item1 = source[sourceIdx++];
+ } while (!predicate(items.Item1));
+
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Length)
+ {
+ return AllocateAndAssign(count, items.Item1);
+ }
+ items.Item2 = source[sourceIdx++];
+ }
+ while (!predicate(items.Item2));
+
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Length)
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2);
+ }
+ items.Item3 = source[sourceIdx++];
+ }
+ while (!predicate(items.Item3));
+
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Length)
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3);
+ }
+ items.Item4 = source[sourceIdx++];
+ }
+ while (!predicate(items.Item4));
+
+ ++count;
+
+ T[] result;
+ if (count >= maxSizeForNoAllocations)
+ {
+ if (sourceIdx < source.Length)
+ {
+ result = FinishViaAllocations(source, source.Length, ref sourceIdx, predicate, null, count);
+ }
+ else
+ {
+ result = Allocate(count);
+ }
+ }
+ else
+ {
+ result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, count);
+ }
+
+ return Assign(result, ref items, count);
+ }
+
+ protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector)
+ {
+ Debug.Assert(predicate != null);
+
+ T[] array = (T[])source;
+ Func typedPredicate = (Func)predicate;
+
+ var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, typedPredicate);
+
+ return (bufferIdx, sourceIdx < array.Length);
+ }
+
+ private static int PopulateBuffer(T[] buffer, T[] source, ref int sourceIdx, Func predicate)
+ {
+ int bufferIdx;
+ int arrayIdx;
+
+ for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Length && bufferIdx < buffer.Length; ++arrayIdx)
+ {
+ var item = source[arrayIdx];
+
+ if (!predicate(item))
+ continue;
+
+ buffer[bufferIdx++] = item;
+ }
+
+ sourceIdx = arrayIdx;
+ return bufferIdx;
+ }
+ }
+
+ internal class ToArrayArrayWhereSelect : ToArrayBase
+ {
+ public static readonly ToArrayArrayWhereSelect Instance = new ToArrayArrayWhereSelect();
+
+ private ToArrayArrayWhereSelect() { }
+
+ public U[] ToArray(T[] source, Func predicate, Func selector)
+ {
+ Debug.Assert(source != null);
+
+ return InitiallyTryWithNoAllocations(source, 0, predicate, selector, 0);
+ }
+
+ private U[] InitiallyTryWithNoAllocations(T[] source, int sourceIdx, Func predicate, Func selector, int count)
+ {
+ T current;
+ (U, U, U, U) items;
+
+ do
+ {
+ if (sourceIdx >= source.Length)
+ {
+ return Allocate(count);
+ }
+
+ current = source[sourceIdx++];
+ } while (!predicate(current));
+
+ items.Item1 = selector(current);
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Length)
+ {
+ return AllocateAndAssign(count, items.Item1);
+ }
+ current = source[sourceIdx++];
+ }
+ while (!predicate(current));
+
+ items.Item2 = selector(current);
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Length)
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2);
+ }
+ current = source[sourceIdx++];
+ }
+ while (!predicate(current));
+
+ items.Item3 = selector(current);
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Length)
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3);
+ }
+ current = source[sourceIdx++];
+ }
+ while (!predicate(current));
+
+ items.Item4 = selector(current);
+ ++count;
+
+ U[] result;
+ if (count >= maxSizeForNoAllocations)
+ {
+ if (sourceIdx < source.Length)
+ {
+ result = FinishViaAllocations(source, source.Length, ref sourceIdx, predicate, selector, count);
+ }
+ else
+ {
+ result = Allocate(count);
+ }
+ }
+ else
+ {
+ result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, selector, count);
+ }
+
+ return Assign(result, ref items, count);
+ }
+
+ protected override (int, bool) PopulateBuffer(U[] buffer, object source, ref int sourceIdx, object? predicate, object? selector)
+ {
+ Debug.Assert(predicate != null);
+
+ T[] array = (T[])source;
+ Func typedPredicate = (Func)predicate!;
+ Func typedSelector = (Func)selector!;
+
+ var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, typedPredicate, typedSelector);
+
+ return (bufferIdx, sourceIdx < array.Length);
+ }
+
+ private static int PopulateBuffer(U[] buffer, T[] source, ref int sourceIdx, Func predicate, Func selector)
+ {
+ int bufferIdx;
+ int arrayIdx;
+
+ for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Length && bufferIdx < buffer.Length; ++arrayIdx)
+ {
+ var item = source[arrayIdx];
+
+ if (!predicate(item))
+ continue;
+
+ buffer[bufferIdx++] = selector(item);
+ }
+
+ sourceIdx = arrayIdx;
+ return bufferIdx;
+ }
+ }
+
+ internal class ToArrayListWhere : ToArrayBase
+ {
+ public static readonly ToArrayListWhere Instance = new ToArrayListWhere();
+
+ private ToArrayListWhere() { }
+
+ public T[] ToArray(List source, Func predicate)
+ {
+ Debug.Assert(source != null);
+
+ return InitiallyTryWithNoAllocations(source, 0, predicate, 0);
+ }
+
+ private T[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func predicate, int count)
+ {
+ (T, T, T, T) items;
+
+ do
+ {
+ if (sourceIdx >= source.Count)
+ {
+ return Allocate(count);
+ }
+
+ items.Item1 = source[sourceIdx++];
+ } while (!predicate(items.Item1));
+
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Count)
+ {
+ return AllocateAndAssign(count, items.Item1);
+ }
+ items.Item2 = source[sourceIdx++];
+ }
+ while (!predicate(items.Item2));
+
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Count)
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2);
+ }
+ items.Item3 = source[sourceIdx++];
+ }
+ while (!predicate(items.Item3));
+
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Count)
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3);
+ }
+ items.Item4 = source[sourceIdx++];
+ }
+ while (!predicate(items.Item4));
+
+ ++count;
+
+ T[] result;
+ if (count >= maxSizeForNoAllocations)
+ {
+ if (sourceIdx < source.Count)
+ {
+ result = FinishViaAllocations(source, source.Count, ref sourceIdx, predicate, null, count);
+ }
+ else
+ {
+ result = Allocate(count);
+ }
+ }
+ else
+ {
+ result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, count);
+ }
+
+ return Assign(result, ref items, count);
+ }
+
+ protected override (int, bool) PopulateBuffer(T[] buffer, object source, ref int sourceIdx, object? predicate, object? selector)
+ {
+ Debug.Assert(predicate != null);
+
+ List array = (List)source;
+ Func typedPredicate = (Func)predicate;
+
+ var bufferIdx = PopulateBuffer(buffer, array, ref sourceIdx, typedPredicate);
+
+ return (bufferIdx, sourceIdx < array.Count);
+ }
+
+ private static int PopulateBuffer(T[] buffer, List source, ref int sourceIdx, Func predicate)
+ {
+ int bufferIdx;
+ int arrayIdx;
+
+ for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Count && bufferIdx < buffer.Length; ++arrayIdx)
+ {
+ var item = source[arrayIdx];
+
+ if (!predicate(item))
+ continue;
+
+ buffer[bufferIdx++] = item;
+ }
+
+ sourceIdx = arrayIdx;
+ return bufferIdx;
+ }
+ }
+
+ internal class ToArrayListWhereSelect : ToArrayBase
+ {
+ public static readonly ToArrayListWhereSelect Instance = new ToArrayListWhereSelect();
+
+ private ToArrayListWhereSelect() { }
+
+ public U[] ToArray(List source, Func predicate, Func selector)
+ {
+ Debug.Assert(source != null);
+
+ return InitiallyTryWithNoAllocations(source, 0, predicate, selector, 0);
+ }
+
+ private U[] InitiallyTryWithNoAllocations(List source, int sourceIdx, Func predicate, Func selector, int count)
+ {
+ T current;
+ (U, U, U, U) items;
+
+ do
+ {
+ if (sourceIdx >= source.Count)
+ {
+ return Allocate(count);
+ }
+
+ current = source[sourceIdx++];
+ } while (!predicate(current));
+
+ items.Item1 = selector(current);
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Count)
+ {
+ return AllocateAndAssign(count, items.Item1);
+ }
+ current = source[sourceIdx++];
+ }
+ while (!predicate(current));
+
+ items.Item2 = selector(current);
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Count)
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2);
+ }
+ current = source[sourceIdx++];
+ }
+ while (!predicate(current));
+
+ items.Item3 = selector(current);
+ ++count;
+
+ do
+ {
+ if (sourceIdx >= source.Count)
+ {
+ return AllocateAndAssign(count, items.Item1, items.Item2, items.Item3);
+ }
+ current = source[sourceIdx++];
+ }
+ while (!predicate(current));
+
+ items.Item4 = selector(current);
+ ++count;
+
+ U[] result;
+ if (count >= maxSizeForNoAllocations)
+ {
+ if (sourceIdx < source.Count)
+ {
+ result = FinishViaAllocations(source, source.Count, ref sourceIdx, predicate, selector, count);
+ }
+ else
+ {
+ result = Allocate(count);
+ }
+ }
+ else
+ {
+ result = InitiallyTryWithNoAllocations(source, sourceIdx, predicate, selector, count);
+ }
+
+ return Assign(result, ref items, count);
+ }
+
+ protected override (int, bool) PopulateBuffer(U[] buffer, object source, ref int sourceIdx, object? predicate, object? selector)
+ {
+ Debug.Assert(predicate != null);
+
+ List list = (List)source;
+ Func typedPredicate = (Func)predicate!;
+ Func typedSelector = (Func)selector!;
+
+ var bufferIdx = PopulateBuffer(buffer, list, ref sourceIdx, typedPredicate, typedSelector);
+
+ return (bufferIdx, sourceIdx < list.Count);
+ }
+
+ private static int PopulateBuffer(U[] buffer, List source, ref int sourceIdx, Func predicate, Func selector)
+ {
+ int bufferIdx;
+ int arrayIdx;
+
+ for (arrayIdx = sourceIdx, bufferIdx = 0; arrayIdx < source.Count && bufferIdx < buffer.Length; ++arrayIdx)
+ {
+ var item = source[arrayIdx];
+
+ if (!predicate(item))
+ continue;
+
+ buffer[bufferIdx++] = selector(item);
+ }
+
+ sourceIdx = arrayIdx;
+ return bufferIdx;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Linq/src/System.Linq.csproj b/src/libraries/System.Linq/src/System.Linq.csproj
index 1649b1c7bd4dd0..869afd6422e280 100644
--- a/src/libraries/System.Linq/src/System.Linq.csproj
+++ b/src/libraries/System.Linq/src/System.Linq.csproj
@@ -32,6 +32,12 @@
System\Collections\Generic\LargeArrayBuilder.SpeedOpt.cs
+
+ System\Collections\Generic\EnumerableHelpers.Linq.SpeedOpt.cs
+
+
+ System\Collections\Generic\ToArray.SpeedOpt.cs
+
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 74a7ab667603c5..d8bb87a0ea4557 100644
--- a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs
@@ -21,17 +21,7 @@ static partial void CreateSelectIPartitionIterator(
private sealed partial class SelectEnumerableIterator : IIListProvider
{
- public TResult[] ToArray()
- {
- var builder = new LargeArrayBuilder(initialize: true);
-
- foreach (TSource item in _source)
- {
- builder.Add(_selector(item));
- }
-
- return builder.ToArray();
- }
+ public TResult[] ToArray() => ToArrayEnumerableWithSelect.Instance.ToArray(_source, _selector);
public List ToList()
{
@@ -597,12 +587,7 @@ private TResult[] LazyToArray()
{
Debug.Assert(_source.GetCount(onlyIfCheap: true) == -1);
- var builder = new LargeArrayBuilder(initialize: true);
- foreach (TSource input in _source)
- {
- builder.Add(_selector(input));
- }
- return builder.ToArray();
+ return ToArrayEnumerableWithSelect.Instance.ToArray(_source, _selector);
}
private TResult[] PreallocatingToArray(int count)
diff --git a/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs
index 83084e4353fc0f..28a37e65f4962a 100644
--- a/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs
+++ b/src/libraries/System.Linq/src/System/Linq/Where.SpeedOpt.cs
@@ -33,20 +33,7 @@ public int GetCount(bool onlyIfCheap)
return count;
}
- public TSource[] ToArray()
- {
- var builder = new LargeArrayBuilder(initialize: true);
-
- foreach (TSource item in _source)
- {
- if (_predicate(item))
- {
- builder.Add(item);
- }
- }
-
- return builder.ToArray();
- }
+ public TSource[] ToArray() => ToArrayEnumerable.Instance.ToArray(_source, _predicate);
public List ToList()
{
@@ -89,20 +76,7 @@ public int GetCount(bool onlyIfCheap)
return count;
}
- public TSource[] ToArray()
- {
- var builder = new LargeArrayBuilder(_source.Length);
-
- foreach (TSource item in _source)
- {
- if (_predicate(item))
- {
- builder.Add(item);
- }
- }
-
- return builder.ToArray();
- }
+ public TSource[] ToArray() => ToArrayArrayWhere.Instance.ToArray(_source, _predicate);
public List ToList()
{
@@ -146,21 +120,7 @@ public int GetCount(bool onlyIfCheap)
return count;
}
- public TSource[] ToArray()
- {
- var builder = new LargeArrayBuilder(_source.Count);
-
- for (int i = 0; i < _source.Count; i++)
- {
- TSource item = _source[i];
- if (_predicate(item))
- {
- builder.Add(item);
- }
- }
-
- return builder.ToArray();
- }
+ public TSource[] ToArray() => ToArrayListWhere.Instance.ToArray(_source, _predicate);
public List ToList()
{
@@ -208,20 +168,7 @@ public int GetCount(bool onlyIfCheap)
return count;
}
- public TResult[] ToArray()
- {
- var builder = new LargeArrayBuilder(_source.Length);
-
- foreach (TSource item in _source)
- {
- if (_predicate(item))
- {
- builder.Add(_selector(item));
- }
- }
-
- return builder.ToArray();
- }
+ public TResult[] ToArray() => ToArrayArrayWhereSelect.Instance.ToArray(_source, _predicate, _selector);
public List ToList()
{
@@ -269,21 +216,7 @@ public int GetCount(bool onlyIfCheap)
return count;
}
- public TResult[] ToArray()
- {
- var builder = new LargeArrayBuilder(_source.Count);
-
- for (int i = 0; i < _source.Count; i++)
- {
- TSource item = _source[i];
- if (_predicate(item))
- {
- builder.Add(_selector(item));
- }
- }
-
- return builder.ToArray();
- }
+ public TResult[] ToArray() => ToArrayListWhereSelect.Instance.ToArray(_source, _predicate, _selector);
public List ToList()
{
@@ -331,20 +264,7 @@ public int GetCount(bool onlyIfCheap)
return count;
}
- public TResult[] ToArray()
- {
- var builder = new LargeArrayBuilder(initialize: true);
-
- foreach (TSource item in _source)
- {
- if (_predicate(item))
- {
- builder.Add(_selector(item));
- }
- }
-
- return builder.ToArray();
- }
+ public TResult[] ToArray() => ToArrayEnumerableWithSelect.Instance.ToArray(_source, _predicate, _selector);
public List ToList()
{