diff --git a/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs b/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs index c947e2c762ff64..fc2737385c4541 100644 --- a/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs +++ b/src/coreclr/tools/Common/System/Collections/Generic/ArrayBuilder.cs @@ -87,6 +87,30 @@ 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); + + collection.CopyTo(_items, _count); + _count += count; + } + } + else + { + foreach (T item in items) + { + Add(item); + } + } + } + public void ZeroExtend(int numItems) { Debug.Assert(numItems >= 0); diff --git a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs index ec862f46d5c3f1..b5419ee03ec8d5 100644 --- a/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs +++ b/src/libraries/Common/src/System/Collections/Generic/ArrayBuilder.cs @@ -64,12 +64,43 @@ public void Add(T item) { if (_count == Capacity) { - EnsureCapacity(_count + 1); + Grow(_count + 1); } 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); + + if (items is ICollection collection) + { + int count = collection.Count; + if (count > 0) + { + if (_count + count > Capacity) + { + Grow(_count + count); + } + + collection.CopyTo(_array!, _count); + _count += count; + } + } + else + { + foreach (T item in items) + { + Add(item); + } + } + } + /// /// Gets the first item in this builder. /// @@ -136,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); 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..f33c750c736b42 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,140 @@ 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)); + } + + [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(); 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);