From 48f079af32ce72a93075148210b9cc3dcaf41fb6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Dec 2025 02:16:53 +0000
Subject: [PATCH 1/6] Initial plan
From 2076a3922ec15a5b97f24b14bee3ce1bf5a5e99b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Dec 2025 02:48:58 +0000
Subject: [PATCH 2/6] Add AddRange method to ArrayBuilder and refactor
Directory, DirectoryInfo, and Timer to use it
Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com>
---
.../Collections/Generic/ArrayBuilder.cs | 14 +++++++
.../Collections/Generic/ArrayBuilderTests.cs | 41 +++++++++++++++++++
.../src/System/IO/Directory.cs | 18 ++++++--
.../src/System/IO/DirectoryInfo.cs | 18 ++++++--
.../src/System/Threading/Timer.cs | 10 ++++-
5 files changed, 94 insertions(+), 7 deletions(-)
diff --git a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
index ec862f46d5c3f1..b720d5a366c732 100644
--- a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
+++ b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
@@ -70,6 +70,20 @@ public void Add(T item)
UncheckedAdd(item);
}
+ ///
+ /// Adds a range of items to the backing array, resizing it as necessary.
+ ///
+ /// The items to add.
+ public void AddRange(IEnumerable items)
+ {
+ Debug.Assert(items != null);
+
+ foreach (T item in items)
+ {
+ Add(item);
+ }
+ }
+
///
/// Gets the first item in this builder.
///
diff --git a/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs b/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs
index b82ed6509155b6..a1f5345b393aa9 100644
--- a/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs
+++ b/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs
@@ -112,6 +112,47 @@ public void UncheckedAdd(int capacity)
VerifyBuilderContents(Enumerable.Repeat(default(T), capacity), builder);
}
+ [Theory]
+ [MemberData(nameof(EnumerableData))]
+ public void AddRange(IEnumerable seed)
+ {
+ var builder = new ArrayBuilder();
+ builder.AddRange(seed);
+
+ int count = builder.Count;
+ T[] array = builder.ToArray();
+
+ Assert.Equal(count, array.Length);
+ Assert.Equal(seed, array);
+ }
+
+ [Fact]
+ public void AddRange_EmptyEnumerable()
+ {
+ var builder = new ArrayBuilder();
+ builder.AddRange(Enumerable.Empty());
+
+ Assert.Equal(0, builder.Count);
+ Assert.Same(Array.Empty(), builder.ToArray());
+ }
+
+ [Theory]
+ [MemberData(nameof(EnumerableData))]
+ public void AddRange_AfterAdd(IEnumerable seed)
+ {
+ var builder = new ArrayBuilder();
+ builder.Add(default(T));
+ builder.AddRange(seed);
+
+ int expectedCount = 1 + seed.Count();
+ Assert.Equal(expectedCount, builder.Count);
+
+ T[] array = builder.ToArray();
+ Assert.Equal(expectedCount, array.Length);
+ Assert.Equal(default(T), array[0]);
+ Assert.Equal(seed, array.Skip(1));
+ }
+
public static TheoryData CapacityData()
{
var data = new TheoryData();
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs
index 78715de118e537..92eb76c35c8bf8 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs
@@ -171,7 +171,11 @@ public static string[] GetFiles(string path, string searchPattern, SearchOption
=> GetFiles(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
public static string[] GetFiles(string path, string searchPattern, EnumerationOptions enumerationOptions)
- => new List(InternalEnumeratePaths(path, searchPattern, SearchTarget.Files, enumerationOptions)).ToArray();
+ {
+ ArrayBuilder builder = default;
+ builder.AddRange(InternalEnumeratePaths(path, searchPattern, SearchTarget.Files, enumerationOptions));
+ return builder.ToArray();
+ }
public static string[] GetDirectories(string path) => GetDirectories(path, "*", enumerationOptions: EnumerationOptions.Compatible);
@@ -181,7 +185,11 @@ public static string[] GetDirectories(string path, string searchPattern, SearchO
=> GetDirectories(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
public static string[] GetDirectories(string path, string searchPattern, EnumerationOptions enumerationOptions)
- => new List(InternalEnumeratePaths(path, searchPattern, SearchTarget.Directories, enumerationOptions)).ToArray();
+ {
+ ArrayBuilder builder = default;
+ builder.AddRange(InternalEnumeratePaths(path, searchPattern, SearchTarget.Directories, enumerationOptions));
+ return builder.ToArray();
+ }
public static string[] GetFileSystemEntries(string path) => GetFileSystemEntries(path, "*", enumerationOptions: EnumerationOptions.Compatible);
@@ -191,7 +199,11 @@ public static string[] GetFileSystemEntries(string path, string searchPattern, S
=> GetFileSystemEntries(path, searchPattern, EnumerationOptions.FromSearchOption(searchOption));
public static string[] GetFileSystemEntries(string path, string searchPattern, EnumerationOptions enumerationOptions)
- => new List(InternalEnumeratePaths(path, searchPattern, SearchTarget.Both, enumerationOptions)).ToArray();
+ {
+ ArrayBuilder builder = default;
+ builder.AddRange(InternalEnumeratePaths(path, searchPattern, SearchTarget.Both, enumerationOptions));
+ return builder.ToArray();
+ }
internal static IEnumerable InternalEnumeratePaths(
string path,
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs
index 835ab22793730c..9ab3d9ba083105 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs
@@ -115,7 +115,11 @@ public FileInfo[] GetFiles(string searchPattern, SearchOption searchOption)
=> GetFiles(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
public FileInfo[] GetFiles(string searchPattern, EnumerationOptions enumerationOptions)
- => new List((IEnumerable)InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Files, enumerationOptions)).ToArray();
+ {
+ ArrayBuilder builder = default;
+ builder.AddRange((IEnumerable)InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Files, enumerationOptions));
+ return builder.ToArray();
+ }
// Returns an array of strongly typed FileSystemInfo entries which will contain a listing
// of all the files and directories.
@@ -130,7 +134,11 @@ public FileSystemInfo[] GetFileSystemInfos(string searchPattern, SearchOption se
=> GetFileSystemInfos(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
public FileSystemInfo[] GetFileSystemInfos(string searchPattern, EnumerationOptions enumerationOptions)
- => new List(InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Both, enumerationOptions)).ToArray();
+ {
+ ArrayBuilder builder = default;
+ builder.AddRange(InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Both, enumerationOptions));
+ return builder.ToArray();
+ }
// Returns an array of Directories in the current directory.
public DirectoryInfo[] GetDirectories() => GetDirectories("*", enumerationOptions: EnumerationOptions.Compatible);
@@ -143,7 +151,11 @@ public DirectoryInfo[] GetDirectories(string searchPattern, SearchOption searchO
=> GetDirectories(searchPattern, EnumerationOptions.FromSearchOption(searchOption));
public DirectoryInfo[] GetDirectories(string searchPattern, EnumerationOptions enumerationOptions)
- => new List((IEnumerable)InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Directories, enumerationOptions)).ToArray();
+ {
+ ArrayBuilder builder = default;
+ builder.AddRange((IEnumerable)InternalEnumerateInfos(FullPath, searchPattern, SearchTarget.Directories, enumerationOptions));
+ return builder.ToArray();
+ }
public IEnumerable EnumerateDirectories()
=> EnumerateDirectories("*", enumerationOptions: EnumerationOptions.Compatible);
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs
index c9747d2cbdec55..497eb078bc57f0 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs
@@ -103,7 +103,15 @@ public TimerQueueDebuggerTypeProxy(TimerQueue queue)
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
- public TimerQueueTimer[] Items => new List(_queue.GetTimersForDebugger()).ToArray();
+ public TimerQueueTimer[] Items
+ {
+ get
+ {
+ ArrayBuilder builder = default;
+ builder.AddRange(_queue.GetTimersForDebugger());
+ return builder.ToArray();
+ }
+ }
}
#endregion
From c6ef6461a446b129c947adbc05adbc3b2777562a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Dec 2025 02:56:19 +0000
Subject: [PATCH 3/6] Optimize ArrayBuilder.AddRange to pre-allocate for
ICollection
Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com>
---
.../Collections/Generic/ArrayBuilder.cs | 23 +++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
index b720d5a366c732..1e6d11880235c2 100644
--- a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
+++ b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
@@ -78,9 +78,28 @@ public void AddRange(IEnumerable items)
{
Debug.Assert(items != null);
- foreach (T item in items)
+ if (items is ICollection collection)
{
- Add(item);
+ int count = collection.Count;
+ if (count > 0)
+ {
+ if (_count + count > Capacity)
+ {
+ EnsureCapacity(_count + count);
+ }
+
+ foreach (T item in collection)
+ {
+ UncheckedAdd(item);
+ }
+ }
+ }
+ else
+ {
+ foreach (T item in items)
+ {
+ Add(item);
+ }
}
}
From 986817d257107aa662e08213934898b6953f3a9a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 5 Dec 2025 03:33:10 +0000
Subject: [PATCH 4/6] Add tests for ICollection optimization in
ArrayBuilder.AddRange
Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com>
---
.../Collections/Generic/ArrayBuilder.cs | 26 ++++++
.../Collections/Generic/ArrayBuilderTests.cs | 93 +++++++++++++++++++
2 files changed, 119 insertions(+)
diff --git a/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs b/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs
index c947e2c762ff64..3732156e697568 100644
--- a/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs
+++ b/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs
@@ -87,6 +87,32 @@ public void Append(ArrayBuilder newItems)
_count += newItems.Count;
}
+ public void AddRange(IEnumerable items)
+ {
+ Debug.Assert(items != null);
+
+ if (items is ICollection collection)
+ {
+ int count = collection.Count;
+ if (count > 0)
+ {
+ EnsureCapacity(_count + count);
+
+ foreach (T item in collection)
+ {
+ _items[_count++] = item;
+ }
+ }
+ }
+ else
+ {
+ foreach (T item in items)
+ {
+ Add(item);
+ }
+ }
+ }
+
public void ZeroExtend(int numItems)
{
Debug.Assert(numItems >= 0);
diff --git a/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs b/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs
index a1f5345b393aa9..f33c750c736b42 100644
--- a/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs
+++ b/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs
@@ -153,6 +153,99 @@ public void AddRange_AfterAdd(IEnumerable seed)
Assert.Equal(seed, array.Skip(1));
}
+ [Theory]
+ [MemberData(nameof(CountData))]
+ public void AddRange_ICollection_PreallocatesCapacity(int count)
+ {
+ // Use a List which implements ICollection
+ List collection = s_generator.GenerateEnumerable(count).ToList();
+
+ var builder = new ArrayBuilder();
+ builder.AddRange(collection);
+
+ Assert.Equal(count, builder.Count);
+
+ // When adding an ICollection, capacity should be at least
+ // enough for the collection (0 if empty)
+ if (count > 0)
+ {
+ Assert.True(builder.Capacity >= count);
+ }
+ else
+ {
+ Assert.Equal(0, builder.Capacity);
+ }
+
+ Assert.Equal(collection, builder.ToArray());
+ }
+
+ [Fact]
+ public void AddRange_ICollection_EmptyCollection()
+ {
+ // Use an empty List which implements ICollection
+ List emptyCollection = new List();
+
+ var builder = new ArrayBuilder();
+ builder.AddRange(emptyCollection);
+
+ Assert.Equal(0, builder.Count);
+ Assert.Equal(0, builder.Capacity);
+ Assert.Same(Array.Empty(), builder.ToArray());
+ }
+
+ [Theory]
+ [MemberData(nameof(CountData))]
+ public void AddRange_ICollection_AfterExistingItems(int count)
+ {
+ // Add some initial items
+ var builder = new ArrayBuilder();
+ builder.Add(default(T));
+ builder.Add(default(T));
+ int initialCount = 2;
+ int initialCapacity = builder.Capacity;
+
+ // Use a List which implements ICollection
+ List collection = s_generator.GenerateEnumerable(count).ToList();
+ builder.AddRange(collection);
+
+ int expectedCount = initialCount + count;
+ Assert.Equal(expectedCount, builder.Count);
+
+ // When adding ICollection after existing items, capacity should be
+ // at least enough for all items
+ Assert.True(builder.Capacity >= expectedCount);
+
+ T[] array = builder.ToArray();
+ Assert.Equal(expectedCount, array.Length);
+ Assert.Equal(default(T), array[0]);
+ Assert.Equal(default(T), array[1]);
+ Assert.Equal(collection, array.Skip(2));
+ }
+
+ [Theory]
+ [MemberData(nameof(CountData))]
+ public void AddRange_NonICollection_GrowsIncrementally(int count)
+ {
+ // Use a generator method to create a pure IEnumerable that is NOT an ICollection
+ IEnumerable NonCollectionEnumerable()
+ {
+ for (int i = 0; i < count; i++)
+ {
+ yield return default(T);
+ }
+ }
+
+ var builder = new ArrayBuilder();
+ builder.AddRange(NonCollectionEnumerable());
+
+ Assert.Equal(count, builder.Count);
+
+ // Non-ICollection path should grow incrementally (powers of 2)
+ Assert.Equal(CalculateExpectedCapacity(count), builder.Capacity);
+
+ Assert.Equal(NonCollectionEnumerable(), builder.ToArray());
+ }
+
public static TheoryData CapacityData()
{
var data = new TheoryData();
From 9491f524b18dd36620cbbe1e588e95abd3717c6e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Dec 2025 18:21:55 +0000
Subject: [PATCH 5/6] Use ICollection.CopyTo for better performance and revert
Timer.cs change
Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com>
---
.../Common/System/Collections/Generic/ArrayBuilder.cs | 6 ++----
.../src/System/Collections/Generic/ArrayBuilder.cs | 6 ++----
.../src/System/Threading/Timer.cs | 10 +---------
3 files changed, 5 insertions(+), 17 deletions(-)
diff --git a/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs b/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs
index 3732156e697568..fc2737385c4541 100644
--- a/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs
+++ b/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs
@@ -98,10 +98,8 @@ public void AddRange(IEnumerable items)
{
EnsureCapacity(_count + count);
- foreach (T item in collection)
- {
- _items[_count++] = item;
- }
+ collection.CopyTo(_items, _count);
+ _count += count;
}
}
else
diff --git a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
index 1e6d11880235c2..765589b4ca4dde 100644
--- a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
+++ b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
@@ -88,10 +88,8 @@ public void AddRange(IEnumerable items)
EnsureCapacity(_count + count);
}
- foreach (T item in collection)
- {
- UncheckedAdd(item);
- }
+ collection.CopyTo(_array!, _count);
+ _count += count;
}
}
else
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs
index 497eb078bc57f0..c9747d2cbdec55 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs
@@ -103,15 +103,7 @@ public TimerQueueDebuggerTypeProxy(TimerQueue queue)
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
- public TimerQueueTimer[] Items
- {
- get
- {
- ArrayBuilder builder = default;
- builder.AddRange(_queue.GetTimersForDebugger());
- return builder.ToArray();
- }
- }
+ public TimerQueueTimer[] Items => new List(_queue.GetTimersForDebugger()).ToArray();
}
#endregion
From 7c669d1d384fdb2f9be187bdf6c16eeb1cec8f34 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Dec 2025 18:45:36 +0000
Subject: [PATCH 6/6] Rename EnsureCapacity to Grow for clarity in Common
ArrayBuilder
Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com>
---
.../Common/src/System/Collections/Generic/ArrayBuilder.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
index 765589b4ca4dde..b5419ee03ec8d5 100644
--- a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
+++ b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs
@@ -64,7 +64,7 @@ public void Add(T item)
{
if (_count == Capacity)
{
- EnsureCapacity(_count + 1);
+ Grow(_count + 1);
}
UncheckedAdd(item);
@@ -85,7 +85,7 @@ public void AddRange(IEnumerable items)
{
if (_count + count > Capacity)
{
- EnsureCapacity(_count + count);
+ Grow(_count + count);
}
collection.CopyTo(_array!, _count);
@@ -167,7 +167,7 @@ public void UncheckedAdd(T item)
_array![_count++] = item;
}
- private void EnsureCapacity(int minimum)
+ private void Grow(int minimum)
{
Debug.Assert(minimum > Capacity);