From 82b62b0a41f4254d4ec7f03777e0e626d2682dfc Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 28 May 2026 18:14:33 +0100 Subject: [PATCH 1/2] perf: replace LINQ Any with foreach in TestGenericTypeResolver (#6044) Avoid delegate and IEnumerator allocations on the generic test resolution hot path during discovery. --- TUnit.Engine/Services/TestGenericTypeResolver.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/TUnit.Engine/Services/TestGenericTypeResolver.cs b/TUnit.Engine/Services/TestGenericTypeResolver.cs index f6a5e216dc..ba99c96557 100644 --- a/TUnit.Engine/Services/TestGenericTypeResolver.cs +++ b/TUnit.Engine/Services/TestGenericTypeResolver.cs @@ -133,7 +133,19 @@ private static Type[] ResolveMethodGenericArguments( { // Handle the case where some parameter types are Object (placeholders for generic parameters) // This happens when data sources provide untyped data or when we have mixed generic/non-generic parameters - if (parameterTypes.Any(t => t == typeof(object)) && methodArguments.Length > 0) + var hasObjectParameter = false; + if (methodArguments.Length > 0) + { + foreach (var parameterType in parameterTypes) + { + if (parameterType == typeof(object)) + { + hasObjectParameter = true; + break; + } + } + } + if (hasObjectParameter) { // Check if this is a simple generic method with one type parameter and mixed parameters if (genericMethodInfo.ParameterNames.Length == 1 && parameterTypes.Length >= 1) From 6b2c7a7401ef3b8b2440058fdfe385b0da4297ee Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 28 May 2026 18:33:21 +0100 Subject: [PATCH 2/2] perf: also drop LINQ All in TestGenericTypeResolver fallback Convert the second `parameterTypes.All(t => t == typeof(object))` call to a foreach with a hoisted `methodArguments.Length > 0` guard, mirroring the prior `Any` substitution. Eliminates the remaining per-call delegate and enumerator allocation on this hot path. Per reviewer feedback on PR #6072. --- TUnit.Engine/Services/TestGenericTypeResolver.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/TUnit.Engine/Services/TestGenericTypeResolver.cs b/TUnit.Engine/Services/TestGenericTypeResolver.cs index ba99c96557..6c4d26e578 100644 --- a/TUnit.Engine/Services/TestGenericTypeResolver.cs +++ b/TUnit.Engine/Services/TestGenericTypeResolver.cs @@ -205,7 +205,20 @@ private static Type[] ResolveMethodGenericArguments( // Handle the case where all parameter types are Object // This happens when data sources provide untyped data - if (parameterTypes.All(t => t == typeof(object)) && methodArguments.Length > 0) + var allParametersAreObject = methodArguments.Length > 0; + if (allParametersAreObject) + { + foreach (var t in parameterTypes) + { + if (t != typeof(object)) + { + allParametersAreObject = false; + break; + } + } + } + + if (allParametersAreObject) { // For the AggregateBy test case with 3 generic parameters if (genericMethodInfo.ParameterNames.Length == 3 &&