Skip to content

Commit 6a3dbf1

Browse files
Avoid loading System.Threading.Tasks.Extensions when not needed by @Youssef1313 in #5694 (backport to rel/3.9) (#5695)
Co-authored-by: Youssef1313 <[email protected]>
1 parent 7ae54da commit 6a3dbf1

File tree

2 files changed

+34
-29
lines changed

2 files changed

+34
-29
lines changed

src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -417,14 +417,10 @@ private async Task<TestResult> ExecuteInternalAsync(object?[]? arguments, Cancel
417417

418418
if (executionContext is null)
419419
{
420-
object? invokeResult = TestMethod.GetInvokeResult(_classInstance, arguments);
421-
if (invokeResult is Task task)
420+
Task? invokeResult = TestMethod.GetInvokeResultAsync(_classInstance, arguments);
421+
if (invokeResult is not null)
422422
{
423-
await task;
424-
}
425-
else if (invokeResult is ValueTask valueTask)
426-
{
427-
await valueTask;
423+
await invokeResult;
428424
}
429425
}
430426
else
@@ -436,14 +432,10 @@ private async Task<TestResult> ExecuteInternalAsync(object?[]? arguments, Cancel
436432
{
437433
try
438434
{
439-
object? invokeResult = TestMethod.GetInvokeResult(_classInstance, arguments);
440-
if (invokeResult is Task task)
441-
{
442-
await task;
443-
}
444-
else if (invokeResult is ValueTask valueTask)
435+
Task? invokeResult = TestMethod.GetInvokeResultAsync(_classInstance, arguments);
436+
if (invokeResult is not null)
445437
{
446-
await valueTask;
438+
await invokeResult;
447439
}
448440
}
449441
catch (Exception e)

src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,17 @@ internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool
9191
/// <returns>True if the method has a void/task return type..</returns>
9292
internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? reflectHelper = null)
9393
=> ReflectHelper.MatchReturnType(method, typeof(Task))
94-
|| ReflectHelper.MatchReturnType(method, typeof(ValueTask))
95-
|| (ReflectHelper.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName(reflectHelper) == null);
94+
|| (ReflectHelper.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName(reflectHelper) == null)
95+
// Keep this the last check, as it avoids loading System.Threading.Tasks.Extensions unnecessarily.
96+
|| method.IsValueTask();
97+
98+
// Avoid loading System.Threading.Tasks.Extensions if not needed.
99+
// Note: .NET runtime will load all types once it's entering the method.
100+
// So, moving this out of the method will load System.Threading.Tasks.Extensions
101+
// Even when invokeResult is null or Task.
102+
[MethodImpl(MethodImplOptions.NoInlining)]
103+
private static bool IsValueTask(this MethodInfo method)
104+
=> ReflectHelper.MatchReturnType(method, typeof(ValueTask));
96105

97106
/// <summary>
98107
/// For async methods compiler generates different type and method.
@@ -107,7 +116,7 @@ internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? re
107116
return asyncStateMachineAttribute?.StateMachineType?.FullName;
108117
}
109118

110-
internal static object? GetInvokeResult(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
119+
internal static Task? GetInvokeResultAsync(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
111120
{
112121
ParameterInfo[]? methodParameters = methodInfo.GetParameters();
113122

@@ -172,9 +181,22 @@ internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? re
172181
}
173182
}
174183

175-
return invokeResult;
184+
return invokeResult switch
185+
{
186+
null => null,
187+
Task t => t,
188+
_ => TryGetTaskFromValueTaskAsync(invokeResult),
189+
};
176190
}
177191

192+
// Avoid loading System.Threading.Tasks.Extensions if not needed.
193+
// Note: .NET runtime will load all types once it's entering the method.
194+
// So, moving this out of the method will load System.Threading.Tasks.Extensions
195+
// Even when invokeResult is null or Task.
196+
[MethodImpl(MethodImplOptions.NoInlining)]
197+
private static Task? TryGetTaskFromValueTaskAsync(object invokeResult)
198+
=> (invokeResult as ValueTask?)?.AsTask();
199+
178200
/// <summary>
179201
/// Invoke a <see cref="MethodInfo"/> as a synchronous <see cref="Task"/>.
180202
/// </summary>
@@ -189,17 +211,8 @@ internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? re
189211
/// </param>
190212
internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
191213
{
192-
object? invokeResult = methodInfo.GetInvokeResult(classInstance, arguments);
193-
194-
// If methodInfo is an async method, wait for returned task
195-
if (invokeResult is Task task)
196-
{
197-
task.GetAwaiter().GetResult();
198-
}
199-
else if (invokeResult is ValueTask valueTask)
200-
{
201-
valueTask.GetAwaiter().GetResult();
202-
}
214+
Task? invokeResult = methodInfo.GetInvokeResultAsync(classInstance, arguments);
215+
invokeResult?.GetAwaiter().GetResult();
203216
}
204217

205218
private static void InferGenerics(Type parameterType, Type argumentType, List<(Type ParameterType, Type Substitution)> result)

0 commit comments

Comments
 (0)