Skip to content

Improve collection and clone performance - #138

Merged
wforney merged 5 commits into
mainfrom
wforney-congenial-couscous
Aug 15, 2026
Merged

Improve collection and clone performance#138
wforney merged 5 commits into
mainfrom
wforney-congenial-couscous

Conversation

@wforney

@wforney wforney commented Aug 15, 2026

Copy link
Copy Markdown
Member

Improves a few hot paths in the shared helpers:\n\n- avoids LINQ allocations in collection find helpers\n- reduces repeated enumeration/counting in enumerable helpers\n- removes repeated rank/lower-bound lookups in deep clone array handling\n- pre-sizes the dependency assembly list\n\nThe change keeps behavior intact while trimming overhead in common utility paths.

Copilot AI lite review requested due to automatic review settings August 15, 2026 02:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request targets micro-optimizations in SharedCode’s shared helper libraries to reduce allocations and repeated work along hot paths (LINQ helpers, enumerable utilities, deep clone, and DI assembly dependency loading).

Changes:

  • Replaces some LINQ-based iterations with explicit loops in collection helpers to reduce allocations.
  • Updates enumerable helper implementations to avoid repeated enumeration/counting.
  • Caches array rank metadata (lengths/lower bounds) in deep-clone array handling.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
SharedCode.DependencyInjection/TypeSourceSelector.cs Attempts to pre-size dependency assembly list for fewer allocations during dependency scanning.
SharedCode.Core/Reflection/DeepCloneGenerator.cs Avoids repeated Enumerable.Range(...).Select(...) allocations when cloning arrays by caching lengths/lower-bounds in arrays.
SharedCode.Core/Linq/EnumerableExtensions.cs Reworks Aggregate/IsNotNullOrEmpty and tweaks sort-expression splitting to reduce overhead.
SharedCode.Core/Linq/CollectionExtensions.cs Removes LINQ Where(...) usage in Find/FindAll and adds list fast-paths to reduce repeated enumeration.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread SharedCode.Core/Linq/EnumerableExtensions.cs Outdated
Comment thread SharedCode.Core/Linq/EnumerableExtensions.cs Outdated
Comment thread SharedCode.DependencyInjection/TypeSourceSelector.cs Outdated
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown

Summary

Summary
Generated on: 8/15/2026 - 3:08:14 AM
Parser: Cobertura
Assemblies: 4
Classes: 195
Files: 158
Line coverage: 42.2% (3247 of 7689)
Covered lines: 3247
Uncovered lines: 4442
Coverable lines: 7689
Total lines: 19674
Branch coverage: 24.9% (1106 of 4428)
Covered branches: 1106
Total branches: 4428
Method coverage: Feature is only available for sponsors

Coverage

SharedCode.Core - 27.3%
Name Line Branch
SharedCode.Core 27.3% 24.6%
SharedCode.AssemblyExtensions 100% 100%
SharedCode.Attributes.DataFormatAttribute 100% 100%
SharedCode.Attributes.DataWidthAttribute 100% 100%
SharedCode.Attributes.StringValueAttribute 100% 100%
SharedCode.BaseException 100% 50%
SharedCode.Calendar.DateTimeExtensions 10.8% 0.7%
SharedCode.Calendar.DateTimeOffsetExtensions 70.3% 44.2%
SharedCode.Calendar.DayOfWeekExtensions 90.9% 87.5%
SharedCode.Collections.ArrayExtensions 92% 73.3%
SharedCode.Collections.Generic.EnumerableExtensions 0% 0%
SharedCode.Collections.Generic.EnumerableExtensions 0% 0%
SharedCode.Collections.Generic.EnumerationUtilities 100%
SharedCode.Collections.ListExtensions 14.2% 12.5%
SharedCode.Collections.ObservableCollectionExtensions 0% 0%
SharedCode.Data.DataReaderExtensions 0% 0%
SharedCode.Data.DataRowExtensions 0% 0%
SharedCode.Data.DataTableExtensions 0% 0%
SharedCode.DateTimeExtensions 0% 0%
SharedCode.DateTimeOffsetExtensions 0% 0%
SharedCode.Domain.Error 88.8%
SharedCode.Domain.ErrorResult 90.9% 100%
SharedCode.Domain.ErrorResult 72.7% 50%
SharedCode.Domain.HttpErrorResult 50%
SharedCode.Domain.Result 100%
SharedCode.Domain.Result 88.8%
SharedCode.Domain.ValidationError 75%
SharedCode.Domain.ValidationErrorResult 80%
SharedCode.Enum 93.2% 83.9%
SharedCode.Enum 50%
SharedCode.EnumExtensions 100%
SharedCode.EventArgs 100%
SharedCode.EventHandlerExtensions 75% 75%
SharedCode.Events.DomainEvent 0%
SharedCode.ExceptionExtensions 51% 32.7%
SharedCode.Extensions 23.6% 14.4%
SharedCode.Extensions 23.6% 14.4%
SharedCode.FluentTimeSpan 66.2% 71.4%
SharedCode.FunctionExtensions 100% 83.3%
SharedCode.FunctionExtensions<T, TResult> 100% 83.3%
SharedCode.IntExtensions 100% 100%
SharedCode.IO.BufferedWriter 0% 0%
SharedCode.IO.DirectoryInfoExtensions 0% 0%
SharedCode.IO.FileInfoExtensions 0% 0%
SharedCode.IO.StreamExtensions 0% 0%
SharedCode.IO.TcpClientExtensions 0% 0%
SharedCode.Linq.CollectionExtensions 58% 50.7%
SharedCode.Linq.CollectionExtensions.SyncChanges 0%
SharedCode.Linq.CollectionExtensions<T, TKey> 58% 50.7%
SharedCode.Linq.CollectionExtensions 58% 50.7%
SharedCode.Linq.EnumerableExtensions 32.3% 25.4%
SharedCode.Linq.EnumerableExtensions<T, TKey> 32.3% 25.4%
SharedCode.Linq.EnumerableExtensions<T, TResult> 32.3% 25.4%
SharedCode.Linq.EnumerableExtensions 32.3% 25.4%
SharedCode.Linq.EnumerableExtensions<TKey, TValue> 32.3% 25.4%
SharedCode.Linq.EnumeratorExtensions 0% 0%
SharedCode.Linq.ExpressionExtensions 0% 0%
SharedCode.Linq.PredicateBuilder 0% 0%
SharedCode.Linq.Predicates 100% 100%
SharedCode.Linq.QueryableExtensions 0%
SharedCode.Linq.QueryableExtensions 0%
SharedCode.Models.Entity 100%
SharedCode.Models.Entity 75% 75%
SharedCode.Notifications.Notification 0% 0%
SharedCode.Notifications.NotificationService 0% 0%
SharedCode.Notifications.ObservableExtensions 0% 0%
SharedCode.Notifications.ObservableExtensions 0% 0%
SharedCode.Notifications.ProducerConsumer 0% 0%
SharedCode.NumberExtensions 100%
SharedCode.Problems.Problem 0% 0%
SharedCode.Problems.ProblemConstants.Types 0%
SharedCode.Problems.ProblemCreationExtensions 0%
SharedCode.Problems.ProblemException 0% 0%
SharedCode.Problems.ProblemJsonConverter 0% 0%
SharedCode.PropertySupport 83.3% 60%
SharedCode.Reflection.DeepCloneCache 0% 0%
SharedCode.Reflection.DeepCloneCache 0% 0%
SharedCode.Reflection.DeepCloneExpressionGenerator 0% 0%
SharedCode.Reflection.DeepCloneGenerator 0% 0%
SharedCode.Reflection.DeepCloneGenerator 0% 0%
SharedCode.Reflection.DeepCloneMsilHelper 0% 0%
SharedCode.Reflection.DeepCloneSafeTypes 0% 0%
SharedCode.Reflection.DeepCloneState 0% 0%
SharedCode.Reflection.DeepCloneState.MiniDictionary 0% 0%
SharedCode.Reflection.ExpressionGenerator 0% 0%
SharedCode.Reflection.GenericExtensions 0% 0%
SharedCode.Reflection.ShallowCloneGenerator 0% 0%
SharedCode.Reflection.ShallowCloneHelper 0% 0%
SharedCode.Reflection.ShallowCloneHelper.ShallowSafeObjectCloneHelper 0%
SharedCode.Security.Hasher 38.6% 44.4%
SharedCode.Specifications.Builders.CacheSpecificationBuilder 50%
SharedCode.Specifications.Builders.IncludableBuilderExtensions 0% 0%
SharedCode.Specifications.Builders.IncludableSpecificationBuilder<T, TPrope
rty>
50%
SharedCode.Specifications.Builders.OrderedBuilderExtensions 50% 25%
SharedCode.Specifications.Builders.OrderedSpecificationBuilder 100%
SharedCode.Specifications.Builders.SpecificationBuilder<T, TResult> 100%
SharedCode.Specifications.Builders.SpecificationBuilder 100%
SharedCode.Specifications.Builders.SpecificationBuilderExtensions 100% 58.3%
SharedCode.Specifications.Evaluators.InMemorySpecificationEvaluator 96.2% 78.5%
SharedCode.Specifications.Evaluators.OrderEvaluator 45.9% 36.2%
SharedCode.Specifications.Evaluators.OrderEvaluator 45.9% 36.2%
SharedCode.Specifications.Evaluators.PaginationEvaluator 50% 37.5%
SharedCode.Specifications.Evaluators.WhereEvaluator 66.6% 37.5%
SharedCode.Specifications.Exceptions.DuplicateOrderChainException 0%
SharedCode.Specifications.Exceptions.DuplicateSkipException 25%
SharedCode.Specifications.Exceptions.DuplicateTakeException 25%
SharedCode.Specifications.Exceptions.SelectorNotFoundException 25%
SharedCode.Specifications.IncludeExpressionInformation 69.2% 40%
SharedCode.Specifications.Specification<T, TResult> 100%
SharedCode.Specifications.Specification 100%
SharedCode.Text.StringBuilderExtensions 100% 100%
SharedCode.Text.StringExtensions 28.3% 28.9%
SharedCode.Text.StringExtensions 28.3% 28.9%
SharedCode.Threading.Tasks.TaskExtensions 92.8%
SharedCode.Threading.Tasks.TaskExtensions 92.8%
SharedCode.Threading.Tasks.ValueTaskExtensions 0%
SharedCode.Threading.Tasks.ValueTaskExtensions<TException, TResult> 0%
SharedCode.Threading.Tasks.ValueTaskExtensions 0%
SharedCode.Threading.Tasks.ValueTaskExtensions 0%
SharedCode.TypeExtensions 33.3% 36%
SharedCode.TypeExtensions 33.3% 36%
SharedCode.Utilities 0% 0%
SharedCode.ValueObject 80% 90.9%
SharedCode.Workflow.Rule 0%
SharedCode.Workflow.Rule 0%
SharedCode.Workflow.RuleSet 0%
SharedCode.Workflow.RuleSet 0%
SharedCode.Xml.XDocumentExtensions 0% 0%
System.Text.RegularExpressions.Generated 0% 0%
System.Text.RegularExpressions.Generated.RunnerFactory
System.Text.RegularExpressions.Generated.RunnerFactory.Runner
SharedCode.Core.Tests - 98.1%
Name Line Branch
SharedCode.Core.Tests 98.1% 77.4%
SharedCode.Tests.AssemblyExtensionsTests 100%
SharedCode.Tests.AssertExtensions 0% 0%
SharedCode.Tests.Attributes.AttributeTests 100% 100%
SharedCode.Tests.BaseExceptionTests 100%
SharedCode.Tests.Calendar.DateTimeExtensionsTests 96.3% 100%
SharedCode.Tests.Calendar.DateTimeOffsetCalendarExtensionsTests 100%
SharedCode.Tests.Calendar.DayOfWeekExtensionsTests 100%
SharedCode.Tests.Collections.ArrayExtensionsTests 100% 100%
SharedCode.Tests.Collections.EnumerationUtilitiesTests 100%
SharedCode.Tests.Domain.ErrorResultTests 100%
SharedCode.Tests.Domain.ResultTests 100%
SharedCode.Tests.EnumExtensionsTests 100%
SharedCode.Tests.EnumTTests 96.2% 90%
SharedCode.Tests.EventHandlerExtensionsTests 100%
SharedCode.Tests.ExceptionExtensionsTests 100%
SharedCode.Tests.ExtensionsTests 100% 100%
SharedCode.Tests.ExtensionsTests.SampleRecord 100%
SharedCode.Tests.FluentTimeSpanTests 100%
SharedCode.Tests.FunctionExtensionsTests 100%
SharedCode.Tests.IntExtensionsTests 100%
SharedCode.Tests.LambdaComparer 0% 0%
SharedCode.Tests.Linq.CollectionExtensionsTests 100% 100%
SharedCode.Tests.Linq.EnumerableExtensionsTests 100% 100%
SharedCode.Tests.Linq.PredicatesTests 100%
SharedCode.Tests.Models.EntityTests 100%
SharedCode.Tests.NumberExtensionsTests 100%
SharedCode.Tests.PropertySupportTests 100% 100%
SharedCode.Tests.PropertySupportTests.SampleClass 100%
SharedCode.Tests.Security.HasherTests 100%
SharedCode.Tests.Specifications.SpecificationTests 97.7% 100%
SharedCode.Tests.Specifications.SpecificationTests.BuilderExposingSpecifica
tion
100%
SharedCode.Tests.Specifications.SpecificationTests.DuplicateSkipSpecificati
on
66.6%
SharedCode.Tests.Specifications.SpecificationTests.DuplicateTakeSpecificati
on
66.6%
SharedCode.Tests.Specifications.SpecificationTests.SampleEntity 100%
SharedCode.Tests.Specifications.SpecificationTests.SampleEntitySpecificatio
n
100% 100%
SharedCode.Tests.Specifications.SpecificationTests.SampleProjectionSpecific
ation
100% 100%
SharedCode.Tests.Specifications.SpecificationTests.SearchOnlySpecification 100%
SharedCode.Tests.Text.StringBuilderExtensionsTests 100%
SharedCode.Tests.Text.StringExtensionsTests 100% 100%
SharedCode.Tests.Threading.TaskExtensionsTests 100%
SharedCode.Tests.TypeExtensionsTests 100%
SharedCode.Tests.ValueObjectTests 96.8%
SharedCode.Tests.ValueObjectTests.Address 66.6%
SharedCode.Tests.ValueObjectTests.MoneyValue 100%
TUnit.Generated.Hooks.SharedCode_Tests_Calendar_DateTimeExtensionsTests_Ini
tTestCase_Before_Test.SharedCode_Tests_Calendar_DateTimeExtensionsTests_Ini
tTestCase_Before_TestInitializer
100%
TUnit.Generated.Hooks.SharedCode_Tests_Calendar_DateTimeExtensionsTests_Tea
rdownTestCase_After_Test.SharedCode_Tests_Calendar_DateTimeExtensionsTests_
TeardownTestCase_After_TestInitializer
100%
SharedCode.Data - 8.3%
Name Line Branch
SharedCode.Data 8.3% 0%
SharedCode.Data.AddOrUpdateDescriptor 0%
SharedCode.Data.CommandRepository 0% 0%
SharedCode.Data.DataReaderExtensions 0% 0%
SharedCode.Data.DataReaderExtensions 0% 0%
SharedCode.Data.DataRecordConverter 0% 0%
SharedCode.Data.DataServiceQueryExtensions 0% 0%
SharedCode.Data.DataServiceQueryExtensions 0% 0%
SharedCode.Data.DataServiceQueryExtensions 0% 0%
SharedCode.Data.DynamicDataRecord 0% 0%
SharedCode.Data.Exceptions.ColumnIsNotInResultSetException 0%
SharedCode.Data.Extensions 0% 0%
SharedCode.Data.ListExtensions 0% 0%
SharedCode.Data.PageBoundry 100%
SharedCode.Data.PagingDescriptor 100%
SharedCode.Data.QueryRepository 0% 0%
SharedCode.Data.QueryResult 100%
SharedCode.Data.Tests - 100%
Name Line Branch
SharedCode.Data.Tests 100% ****
SharedCode.Data.Tests.PageBoundryTests 100%
SharedCode.Data.Tests.PagingDescriptorTests 100%
SharedCode.Data.Tests.QueryResultTests 100%

Copilot AI review requested due to automatic review settings August 15, 2026 02:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

SharedCode.Core/Linq/EnumerableExtensions.cs:268

  • TryGetNonEnumeratedCount is not available on the netstandard2.0/netstandard2.1 targets for this project, so this implementation will not compile for those TFMs. Consider switching to ICollection<T>/IReadOnlyCollection<T> checks and falling back to Any() only for true enumerables.
		@this is not null && (@this.TryGetNonEnumeratedCount(out var count) ? count > 0 : @this.Any());

SharedCode.DependencyInjection/TypeSourceSelector.cs:157

  • assembly.GetReferencedAssemblies() is now called before the surrounding try/catch. If that call throws (e.g., for a dynamic/reflection-only assembly), the method will now throw instead of falling back to returning a selector for just the provided assembly as it did before. To keep the previous fault-tolerant behavior, guard GetReferencedAssemblies() and fall back to an empty array.
        var referencedAssemblies = assembly.GetReferencedAssemblies();

SharedCode.Core/Linq/CollectionExtensions.cs:376

  • RemoveAll currently removes by value (ICollection<T>.Remove(item)), which can remove an earlier equal item when duplicates exist and can cause matches to be skipped (e.g., [A,B,A,A] with match(A) may leave one A). When the collection is indexable, remove by index to ensure the correct element is removed; for non-indexable collections, consider a two-pass approach (collect then remove) to ensure all matches are removed.
            _ = @this.Remove(item);

SharedCode.Core/Linq/EnumerableExtensions.cs:292

  • Changing Split(' ') to Split(' ', RemoveEmptyEntries) changes behavior for inputs with leading spaces (e.g., " Name" previously resulted in no sort because parts[0] was empty, but now it will sort by Name). The PR description says behavior is kept intact; confirm this behavior change is acceptable, or preserve the previous semantics.
		var parts = sortExpression.Split(' ', StringSplitOptions.RemoveEmptyEntries);

Comment thread SharedCode.Core/Linq/EnumerableExtensions.cs Outdated
Copilot AI review requested due to automatic review settings August 15, 2026 02:44
Co-authored-by: Copilot Autofix powered by AI <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

SharedCode.Core/Linq/EnumerableExtensions.cs:293

  • Using StringSplitOptions.RemoveEmptyEntries changes how leading spaces are handled (e.g. " desc" previously resulted in an empty first token and returned the source unchanged; now it treats "desc" as the property name and will likely throw). If behavior is expected to stay intact, consider keeping the original split behavior.
	/// <param name="this">The enumerable.</param>
	/// <param name="sortExpression">The sort expression.</param>
	/// <returns>The sorted enumerable.</returns>

SharedCode.Core/Linq/EnumerableExtensions.cs:63

  • Aggregate(..., defaultValue, aggregateFunction) now throws for aggregateFunction == null even when the source is null or empty. Previously, the method returned defaultValue without needing the function in those cases, so this changes behavior and can break callers that rely on the early default return path.
		_ = aggregateFunction ?? throw new ArgumentNullException(nameof(aggregateFunction));

		if (@this is null)
		{
			return defaultValue;

Comment thread SharedCode.Core/Linq/CollectionExtensions.cs
Copilot AI review requested due to automatic review settings August 15, 2026 02:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (3)

SharedCode.Core/Linq/EnumerableExtensions.cs:275

  • IsNotNullOrEmpty now calls TryGetNonEnumeratedCount, which is not available on the netstandard2.0/netstandard2.1 targets (SharedCode.Core multi-targets those frameworks). This will fail to compile for those TFMs unless guarded with conditional compilation or replaced with a portable approach.
	public static bool IsNotNullOrEmpty<T>(this IEnumerable<T> @this) =>
		@this is not null && (@this.TryGetNonEnumeratedCount(out var count) ? count > 0 : @this.Any());

SharedCode.Core/Linq/EnumerableExtensions.cs:58

  • Aggregate<T>(..., defaultValue, aggregateFunction) uses System.Linq.Enumerable.Aggregate for ICollection<T>/IReadOnlyCollection<T> but a manual aggregation loop for other enumerables. For value types, LINQ’s Aggregate requires a non-null T return and will throw if aggregateFunction returns null, while the manual path can return null (T?). This makes behavior depend on the runtime type of @this.
		if (@this is ICollection<T> collection)
		{
			return collection.Count == 0
			    ? defaultValue
			    : System.Linq.Enumerable.Aggregate(@this, (a, b) => aggregateFunction(a, b)!);

SharedCode.Core/Linq/CollectionExtensions.cs:184

  • In FindIndex (startIndex/count overload), the startIndex range check throws ArgumentNullException, which is incorrect for an out-of-range index and makes exception handling inconsistent for callers.
            if (predicate(@this is IList<T> list ? list[i] : @this.ElementAt(i)))

Copilot AI review requested due to automatic review settings August 15, 2026 03:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

SharedCode.Core/Linq/EnumerableExtensions.cs:275

  • IsNotNullOrEmpty now calls TryGetNonEnumeratedCount, which is not available for the netstandard2.0 / netstandard2.1 targets of SharedCode.Core. This will fail to compile for those TFMs. Consider using ICollection<T> / IReadOnlyCollection<T> count checks (fast path) and only use TryGetNonEnumeratedCount under #if NET6_0_OR_GREATER (or drop it entirely and fall back to Any() for netstandard).
	public static bool IsNotNullOrEmpty<T>(this IEnumerable<T> @this) =>
		@this is not null && (@this.TryGetNonEnumeratedCount(out var count) ? count > 0 : @this.Any());

@wforney
wforney merged commit a88a5a8 into main Aug 15, 2026
14 checks passed
@wforney
wforney deleted the wforney-congenial-couscous branch August 15, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants