From 43228a27500489c79f37197d94f4a33d143d2d92 Mon Sep 17 00:00:00 2001 From: William Forney Date: Fri, 14 Aug 2026 19:11:42 -0700 Subject: [PATCH 1/5] Improve collection and clone performance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- SharedCode.Core/Linq/CollectionExtensions.cs | 28 ++++++++++++------- SharedCode.Core/Linq/EnumerableExtensions.cs | 11 ++++++-- .../Reflection/DeepCloneGenerator.cs | 13 ++++++--- .../TypeSourceSelector.cs | 2 +- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/SharedCode.Core/Linq/CollectionExtensions.cs b/SharedCode.Core/Linq/CollectionExtensions.cs index c09ee5b..8d18776 100644 --- a/SharedCode.Core/Linq/CollectionExtensions.cs +++ b/SharedCode.Core/Linq/CollectionExtensions.cs @@ -70,9 +70,12 @@ public static TCollection AddRangeIfRangeNotNull(this TCollectio _ = @this ?? throw new ArgumentNullException(nameof(@this)); _ = predicate ?? throw new ArgumentNullException(nameof(predicate)); - foreach (var item in @this.Where(item => predicate(item))) + foreach (var item in @this) { - return item; + if (predicate(item)) + { + return item; + } } return default; @@ -92,9 +95,12 @@ public static Collection FindAll(this ICollection @this, Predicate p _ = predicate ?? throw new ArgumentNullException(nameof(predicate)); var all = new Collection(); - foreach (var item in @this.Where(item => predicate(item))) + foreach (var item in @this) { - all.Add(item); + if (predicate(item)) + { + all.Add(item); + } } return all; @@ -175,7 +181,7 @@ public static int FindIndex( throw new ArgumentOutOfRangeException(nameof(@this)); } - if (predicate(@this.ElementAt(i))) + if (predicate(@this is IList list ? list[i] : @this.ElementAt(i))) { return i; } @@ -199,9 +205,10 @@ public static int FindIndex( for (var i = @this.Count - 1; i >= 0; i--) { - if (predicate(@this.ElementAt(i))) + var item = @this is IList list ? list[i] : @this.ElementAt(i); + if (predicate(item)) { - return @this.ElementAt(i); + return item; } } @@ -285,7 +292,7 @@ public static int FindLastIndex( throw new ArgumentOutOfRangeException(nameof(@this)); } - if (predicate(@this.ElementAt(i))) + if (predicate(@this is IList list ? list[i] : @this.ElementAt(i))) { return i; } @@ -360,12 +367,13 @@ public static int RemoveAll(this ICollection @this, Predicate match) var count = 0; for (var i = 0; i < @this.Count; i++) { - if (!match(@this.ElementAt(i))) + var item = @this is IList list ? list[i] : @this.ElementAt(i); + if (!match(item)) { continue; } - _ = @this.Remove(@this.ElementAt(i)); + _ = @this.Remove(item); count++; i--; } diff --git a/SharedCode.Core/Linq/EnumerableExtensions.cs b/SharedCode.Core/Linq/EnumerableExtensions.cs index b5c1137..a29f205 100644 --- a/SharedCode.Core/Linq/EnumerableExtensions.cs +++ b/SharedCode.Core/Linq/EnumerableExtensions.cs @@ -35,7 +35,11 @@ public static class EnumerableExtensions /// The aggregate function. /// The result. public static T? Aggregate(this IEnumerable @this, T? defaultValue, Func aggregateFunction) => - @this?.Any() ?? false ? System.Linq.Enumerable.Aggregate(@this, (a, b) => aggregateFunction(a, b)!) : defaultValue; + @this is null + ? defaultValue + : @this.TryGetNonEnumeratedCount(out var count) && count == 0 + ? defaultValue + : System.Linq.Enumerable.Aggregate(@this, (a, b) => aggregateFunction(a, b)!); /// /// Starts execution of IQueryable on a ThreadPool thread and returns immediately with a @@ -236,7 +240,8 @@ public static int IndexOf(this IEnumerable @this, TSource valu /// /// true if the source enumerable is not null and contains items; otherwise, false. /// - public static bool IsNotNullOrEmpty(this IEnumerable @this) => @this?.Any() == true; + public static bool IsNotNullOrEmpty(this IEnumerable @this) => + @this is not null && (!@this.TryGetNonEnumeratedCount(out var count) || count > 0); /// /// Determines whether the source enumerable is null or contains no items. @@ -260,7 +265,7 @@ public static int IndexOf(this IEnumerable @this, TSource valu public static IEnumerable OrderBy(this IEnumerable @this, string sortExpression) { sortExpression += string.Empty; - var parts = sortExpression.Split(' '); + var parts = sortExpression.Split(' ', StringSplitOptions.RemoveEmptyEntries); var descending = false; if (parts.Length == 0 || string.IsNullOrEmpty(parts[0])) diff --git a/SharedCode.Core/Reflection/DeepCloneGenerator.cs b/SharedCode.Core/Reflection/DeepCloneGenerator.cs index aa6e18f..2e6d3ac 100644 --- a/SharedCode.Core/Reflection/DeepCloneGenerator.cs +++ b/SharedCode.Core/Reflection/DeepCloneGenerator.cs @@ -211,10 +211,15 @@ internal static T[] Clone1DimArraySafeInternal(T[] obj, DeepCloneState state) var rank = obj.Rank; - var lengths = Enumerable.Range(0, rank).Select(obj.GetLength).ToArray(); - - var lowerBounds = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray(); - var idxes = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray(); + var lengths = new int[rank]; + var lowerBounds = new int[rank]; + var idxes = new int[rank]; + for (var i = 0; i < rank; i++) + { + lengths[i] = obj.GetLength(i); + lowerBounds[i] = obj.GetLowerBound(i); + idxes[i] = lowerBounds[i]; + } var elementType = obj.GetType().GetElementType(); var outArray = Array.CreateInstance(elementType!, lengths, lowerBounds); diff --git a/SharedCode.DependencyInjection/TypeSourceSelector.cs b/SharedCode.DependencyInjection/TypeSourceSelector.cs index 297029f..e25cd93 100644 --- a/SharedCode.DependencyInjection/TypeSourceSelector.cs +++ b/SharedCode.DependencyInjection/TypeSourceSelector.cs @@ -154,7 +154,7 @@ public ICatalogSelector FromAssemblyDependencies(Assembly assembly) } #endif - var assemblies = new List { assembly }; + var assemblies = new List(capacity: 1) { assembly }; try { From bbd1c5239f93ace0350c4f1de3e793cfaa2f8b78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Aug 2026 02:39:55 +0000 Subject: [PATCH 2/5] Fix review issues: Aggregate empty-iterator, IsNotNullOrEmpty iterator, List capacity Co-authored-by: wforney <79032+wforney@users.noreply.github.com> --- SharedCode.Core/Linq/EnumerableExtensions.cs | 38 +++++++++++++++---- .../TypeSourceSelector.cs | 5 ++- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/SharedCode.Core/Linq/EnumerableExtensions.cs b/SharedCode.Core/Linq/EnumerableExtensions.cs index a29f205..d8814cc 100644 --- a/SharedCode.Core/Linq/EnumerableExtensions.cs +++ b/SharedCode.Core/Linq/EnumerableExtensions.cs @@ -34,12 +34,36 @@ public static class EnumerableExtensions /// The default value. /// The aggregate function. /// The result. - public static T? Aggregate(this IEnumerable @this, T? defaultValue, Func aggregateFunction) => - @this is null - ? defaultValue - : @this.TryGetNonEnumeratedCount(out var count) && count == 0 - ? defaultValue - : System.Linq.Enumerable.Aggregate(@this, (a, b) => aggregateFunction(a, b)!); + public static T? Aggregate(this IEnumerable @this, T? defaultValue, Func aggregateFunction) + { + _ = aggregateFunction ?? throw new ArgumentNullException(nameof(aggregateFunction)); + + if (@this is null) + { + return defaultValue; + } + + if (@this.TryGetNonEnumeratedCount(out var count)) + { + return count == 0 + ? defaultValue + : System.Linq.Enumerable.Aggregate(@this, (a, b) => aggregateFunction(a, b)!); + } + + using var enumerator = @this.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return defaultValue; + } + + T? result = enumerator.Current; + while (enumerator.MoveNext()) + { + result = aggregateFunction(result, enumerator.Current); + } + + return result; + } /// /// Starts execution of IQueryable on a ThreadPool thread and returns immediately with a @@ -241,7 +265,7 @@ public static int IndexOf(this IEnumerable @this, TSource valu /// true if the source enumerable is not null and contains items; otherwise, false. /// public static bool IsNotNullOrEmpty(this IEnumerable @this) => - @this is not null && (!@this.TryGetNonEnumeratedCount(out var count) || count > 0); + @this is not null && (@this.TryGetNonEnumeratedCount(out var count) ? count > 0 : @this.Any()); /// /// Determines whether the source enumerable is null or contains no items. diff --git a/SharedCode.DependencyInjection/TypeSourceSelector.cs b/SharedCode.DependencyInjection/TypeSourceSelector.cs index e25cd93..25c6001 100644 --- a/SharedCode.DependencyInjection/TypeSourceSelector.cs +++ b/SharedCode.DependencyInjection/TypeSourceSelector.cs @@ -154,11 +154,12 @@ public ICatalogSelector FromAssemblyDependencies(Assembly assembly) } #endif - var assemblies = new List(capacity: 1) { assembly }; + var referencedAssemblies = assembly.GetReferencedAssemblies(); + var assemblies = new List(capacity: referencedAssemblies.Length + 1) { assembly }; try { - foreach (var dependencyName in assembly.GetReferencedAssemblies()) + foreach (var dependencyName in referencedAssemblies) { try { From 305c0508e47a28bd2429816ceaf5e12701261060 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Aug 2026 02:44:20 +0000 Subject: [PATCH 3/5] Fix tab indentation in DeepCloneGenerator and EnumerableExtensions Co-authored-by: wforney <79032+wforney@users.noreply.github.com> --- SharedCode.Core/Linq/EnumerableExtensions.cs | 2 +- SharedCode.Core/Reflection/DeepCloneGenerator.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SharedCode.Core/Linq/EnumerableExtensions.cs b/SharedCode.Core/Linq/EnumerableExtensions.cs index d8814cc..f89a568 100644 --- a/SharedCode.Core/Linq/EnumerableExtensions.cs +++ b/SharedCode.Core/Linq/EnumerableExtensions.cs @@ -265,7 +265,7 @@ public static int IndexOf(this IEnumerable @this, TSource valu /// true if the source enumerable is not null and contains items; otherwise, false. /// public static bool IsNotNullOrEmpty(this IEnumerable @this) => - @this is not null && (@this.TryGetNonEnumeratedCount(out var count) ? count > 0 : @this.Any()); + @this is not null && (@this.TryGetNonEnumeratedCount(out var count) ? count > 0 : @this.Any()); /// /// Determines whether the source enumerable is null or contains no items. diff --git a/SharedCode.Core/Reflection/DeepCloneGenerator.cs b/SharedCode.Core/Reflection/DeepCloneGenerator.cs index 2e6d3ac..16b60d5 100644 --- a/SharedCode.Core/Reflection/DeepCloneGenerator.cs +++ b/SharedCode.Core/Reflection/DeepCloneGenerator.cs @@ -216,9 +216,9 @@ internal static T[] Clone1DimArraySafeInternal(T[] obj, DeepCloneState state) var idxes = new int[rank]; for (var i = 0; i < rank; i++) { - lengths[i] = obj.GetLength(i); - lowerBounds[i] = obj.GetLowerBound(i); - idxes[i] = lowerBounds[i]; + lengths[i] = obj.GetLength(i); + lowerBounds[i] = obj.GetLowerBound(i); + idxes[i] = lowerBounds[i]; } var elementType = obj.GetType().GetElementType(); From 952de6d472a3c75bd49f8f97f1690f683c3cf611 Mon Sep 17 00:00:00 2001 From: Bill Forney Date: Fri, 14 Aug 2026 19:46:19 -0700 Subject: [PATCH 4/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- SharedCode.Core/Linq/EnumerableExtensions.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/SharedCode.Core/Linq/EnumerableExtensions.cs b/SharedCode.Core/Linq/EnumerableExtensions.cs index f89a568..9819bad 100644 --- a/SharedCode.Core/Linq/EnumerableExtensions.cs +++ b/SharedCode.Core/Linq/EnumerableExtensions.cs @@ -43,9 +43,16 @@ public static class EnumerableExtensions return defaultValue; } - if (@this.TryGetNonEnumeratedCount(out var count)) + if (@this is ICollection collection) + { + return collection.Count == 0 + ? defaultValue + : System.Linq.Enumerable.Aggregate(@this, (a, b) => aggregateFunction(a, b)!); + } + + if (@this is IReadOnlyCollection readOnlyCollection) { - return count == 0 + return readOnlyCollection.Count == 0 ? defaultValue : System.Linq.Enumerable.Aggregate(@this, (a, b) => aggregateFunction(a, b)!); } From 92f092c510fe32a48f37adec2122fd337be503f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Aug 2026 03:02:46 +0000 Subject: [PATCH 5/5] Fix RemoveAll to remove IList items by index Co-authored-by: wforney <79032+wforney@users.noreply.github.com> --- SharedCode.Core/Linq/CollectionExtensions.cs | 22 ++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/SharedCode.Core/Linq/CollectionExtensions.cs b/SharedCode.Core/Linq/CollectionExtensions.cs index 8d18776..0c7524c 100644 --- a/SharedCode.Core/Linq/CollectionExtensions.cs +++ b/SharedCode.Core/Linq/CollectionExtensions.cs @@ -367,13 +367,27 @@ public static int RemoveAll(this ICollection @this, Predicate match) var count = 0; for (var i = 0; i < @this.Count; i++) { - var item = @this is IList list ? list[i] : @this.ElementAt(i); - if (!match(item)) + if (@this is IList list) + { + var item = list[i]; + if (!match(item)) + { + continue; + } + + list.RemoveAt(i); + } + else { - continue; + var item = @this.ElementAt(i); + if (!match(item)) + { + continue; + } + + _ = @this.Remove(item); } - _ = @this.Remove(item); count++; i--; }