diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.ArgumentResolution.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.ArgumentResolution.cs
index 6d155adccb..3c4f06ebff 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.ArgumentResolution.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.ArgumentResolution.cs
@@ -10,7 +10,10 @@ internal partial class TestMethodInfo
internal object?[] ResolveArguments(object?[] arguments)
{
- ParameterInfo[] parametersInfo = MethodInfo.GetParameters();
+ // Use the cached ParameterTypes property rather than calling MethodInfo.GetParameters()
+ // (which allocates a fresh ParameterInfo[] on every call). ResolveArguments only reads the
+ // array, so sharing the cached instance is safe.
+ ParameterInfo[] parametersInfo = ParameterTypes;
int requiredParameterCount = 0;
bool hasParamsValue = false;
object? paramsValues = null;
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
index 96b3545d62..a02f455014 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
@@ -35,6 +35,14 @@ internal sealed class TestMethodRunner
///
private readonly TestMethodInfo _testMethodInfo;
+ ///
+ /// Lazily-created wrapper reused across all data rows of a
+ /// data-driven test. Both and
+ /// are immutable for the lifetime of a , so a single wrapper can be shared
+ /// instead of allocating one per row.
+ ///
+ private ReflectionTestMethodInfo? _cachedReflectionMethodInfo;
+
///
/// Initializes a new instance of the class.
///
@@ -423,9 +431,9 @@ private async Task ExecuteTestWithDataSourceAsync(ITestContext exe
// and both GetDisplayName and ComputeDefaultDisplayName need to be consulted.
if (displayNameFromTestDataRow is null && testDataSource is not null)
{
- var reflectionMethodInfo = new ReflectionTestMethodInfo(_testMethodInfo.MethodInfo, _test.DisplayName);
- displayName = testDataSource.GetDisplayName(reflectionMethodInfo, data)
- ?? TestDataSourceUtilities.ComputeDefaultDisplayName(reflectionMethodInfo, data)
+ _cachedReflectionMethodInfo ??= new ReflectionTestMethodInfo(_testMethodInfo.MethodInfo, _test.DisplayName);
+ displayName = testDataSource.GetDisplayName(_cachedReflectionMethodInfo, data)
+ ?? TestDataSourceUtilities.ComputeDefaultDisplayName(_cachedReflectionMethodInfo, data)
?? displayName;
}
else
@@ -488,11 +496,26 @@ private async Task ExecuteTestAsync(ITestContext executionContext,
{
try
{
+ ExecutionContext? capturedContext = testMethodInfo.Parent.ExecutionContext ?? testMethodInfo.Parent.Parent.ExecutionContext;
+
+ // Fast path: when no ExecutionContext was captured by [AssemblyInitialize] / [ClassInitialize]
+ // (the common case), RunOnContext with a null context simply invokes the action directly on the
+ // current thread. Skip the TaskCompletionSource + closure + delegate allocations and just await
+ // the executor directly.
+ if (capturedContext is null)
+ {
+ using (TestContextImplementation.SetCurrentTestContext(executionContext as TestContext))
+ {
+ testMethodInfo.TestContext = executionContext;
+ return await _testMethodInfo.Executor.ExecuteAsync(testMethodInfo).ConfigureAwait(false);
+ }
+ }
+
var tcs = new TaskCompletionSource();
#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
ExecutionContextHelpers.RunOnContext(
- testMethodInfo.Parent.ExecutionContext ?? testMethodInfo.Parent.Parent.ExecutionContext,
+ capturedContext,
async () =>
{
try
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs
index 5efc98a041..a24f169e6c 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs
@@ -524,16 +524,14 @@ private SynchronizedStringBuilder GetTestContextMessagesStringBuilder()
/// A fresh context suitable for one folded data-driven iteration.
internal TestContextImplementation CloneForDataDrivenIteration()
{
- // Take a shallow snapshot of the current property bag so that the clone starts with
- // the same properties (including TestNameLabel / FullyQualifiedTestClassNameLabel and
- // anything merged from AssemblyInitialize / ClassInitialize) but is otherwise isolated.
- // Per-iteration mutations to the clone's property bag won't leak back to this instance
+ // Pass testMethod: null and testClassFullName: null because the relevant labels
+ // (including TestNameLabel / FullyQualifiedTestClassNameLabel and anything merged from
+ // AssemblyInitialize / ClassInitialize) are already in _properties. In that branch the
+ // constructor takes a shallow copy of the passed dictionary, so passing _properties
+ // directly gives the clone an isolated property bag without an extra intermediate copy:
+ // per-iteration mutations to the clone's property bag won't leak back to this instance
// nor to subsequent iterations.
- var snapshot = new Dictionary(_properties);
-
- // Pass testMethod: null and testClassFullName: null because the relevant labels are
- // already in the snapshot. The constructor will copy the snapshot as-is.
- var clone = new TestContextImplementation(testMethod: null, testClassFullName: null, snapshot, _messageLogger, _testRunCancellationToken);
+ var clone = new TestContextImplementation(testMethod: null, testClassFullName: null, _properties, _messageLogger, _testRunCancellationToken);
// Preserve TestRunCount so user code that observes it (e.g. retry-aware tests) sees
// the same value it would see in the unfolded path. TestRunCount represents the
diff --git a/src/TestFramework/TestFramework/Internal/ReflectionTestMethodInfo.cs b/src/TestFramework/TestFramework/Internal/ReflectionTestMethodInfo.cs
index 8d2ffd3632..41b4574690 100644
--- a/src/TestFramework/TestFramework/Internal/ReflectionTestMethodInfo.cs
+++ b/src/TestFramework/TestFramework/Internal/ReflectionTestMethodInfo.cs
@@ -7,6 +7,8 @@ internal sealed class ReflectionTestMethodInfo : MethodInfo
{
private readonly MethodInfo _methodInfo;
+ private ParameterInfo[]? _parameters;
+
public ReflectionTestMethodInfo(MethodInfo methodInfo, string? displayName)
{
_methodInfo = methodInfo;
@@ -41,7 +43,10 @@ public ReflectionTestMethodInfo(MethodInfo methodInfo, string? displayName)
public override MethodImplAttributes GetMethodImplementationFlags() => _methodInfo.GetMethodImplementationFlags();
- public override ParameterInfo[] GetParameters() => _methodInfo.GetParameters();
+ // MethodInfo.GetParameters() allocates a fresh ParameterInfo[] on every call (CLR safety
+ // guarantee). The wrapped MethodInfo is immutable, so cache the array and share it across
+ // callers of this wrapper (e.g. display-name computation for every data-driven row).
+ public override ParameterInfo[] GetParameters() => _parameters ??= _methodInfo.GetParameters();
public override object? Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) => _methodInfo.Invoke(obj, invokeAttr, binder, parameters, culture);