From d5700cb3cc310f209e6142f79069a36a6e176cfe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 13:17:20 +0000 Subject: [PATCH 1/3] perf: cache MethodInfo for asyncCast/taskCast to avoid repeated reflection GetMethod and MakeGenericMethod are called on every API response deserialization. The GetMethod result is constant (same method every time), and MakeGenericMethod produces the same MethodInfo for a given return type. Cache both at module level to eliminate the per-call overhead. - asyncCastMethod / taskCastMethod: computed once at module init - asyncCastCache / taskCastCache: ConcurrentDictionary to memoize MakeGenericMethod per return type Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/SwaggerProvider.Runtime/RuntimeHelpers.fs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs index e5d7ae9e..500aac32 100644 --- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs +++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs @@ -319,10 +319,25 @@ module RuntimeHelpers = if (name <> "Content-Type") then raise <| Exception(errMsg)) + /// Pre-resolved MethodInfo for AsyncExtensions.cast and TaskExtensions.cast. + /// Both are constant across the lifetime of the process; resolve once at module init. + let private asyncCastMethod = typeof.GetMethod "cast" + + let private taskCastMethod = typeof.GetMethod "cast" + + /// Per-type cache of the concrete generic MethodInfo produced by MakeGenericMethod. + /// Avoids repeated generic-method instantiation for the same return type. + let private asyncCastCache = + Collections.Concurrent.ConcurrentDictionary() + + let private taskCastCache = + Collections.Concurrent.ConcurrentDictionary() + let asyncCast runtimeTy (asyncOp: Async) = - let castFn = typeof.GetMethod "cast" + let m = + asyncCastCache.GetOrAdd(runtimeTy, fun t -> asyncCastMethod.MakeGenericMethod([| t |])) - castFn.MakeGenericMethod([| runtimeTy |]).Invoke(null, [| asyncOp |]) + m.Invoke(null, [| asyncOp |]) let readContentAsString (content: HttpContent) (ct: System.Threading.CancellationToken) : Task = #if NET5_0_OR_GREATER @@ -339,6 +354,7 @@ module RuntimeHelpers = #endif let taskCast runtimeTy (task: Task) = - let castFn = typeof.GetMethod "cast" + let m = + taskCastCache.GetOrAdd(runtimeTy, fun t -> taskCastMethod.MakeGenericMethod([| t |])) - castFn.MakeGenericMethod([| runtimeTy |]).Invoke(null, [| task |]) + m.Invoke(null, [| task |]) From c22b5cbd5e29bfff747c4761b9ca8e4796a954ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 11 Apr 2026 13:17:22 +0000 Subject: [PATCH 2/3] ci: trigger checks From 4d20ed56657bb4a8bec5ddcc6c80485dc944f9b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 16:55:40 +0000 Subject: [PATCH 3/3] refactor: use GetMethods+filter for cast method resolution, extract helper Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/e2c0c323-c520-4fce-9df5-6500a5c47e7c Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- src/SwaggerProvider.Runtime/RuntimeHelpers.fs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs index 500aac32..88b7e4ea 100644 --- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs +++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs @@ -319,11 +319,24 @@ module RuntimeHelpers = if (name <> "Content-Type") then raise <| Exception(errMsg)) + /// Resolves a public static generic method definition with one type parameter and one + /// value parameter by name from the given type. Raises a descriptive exception if the + /// method cannot be uniquely identified, avoiding AmbiguousMatchException from a + /// name-only GetMethod lookup. + let private resolveCastMethod(ownerType: Type) = + ownerType.GetMethods(Reflection.BindingFlags.Public ||| Reflection.BindingFlags.Static) + |> Array.tryFind(fun m -> + m.Name = "cast" + && m.IsGenericMethodDefinition + && m.GetGenericArguments().Length = 1 + && m.GetParameters().Length = 1) + |> Option.defaultWith(fun () -> failwithf "Could not resolve %s.cast<'t> generic method definition" ownerType.FullName) + /// Pre-resolved MethodInfo for AsyncExtensions.cast and TaskExtensions.cast. /// Both are constant across the lifetime of the process; resolve once at module init. - let private asyncCastMethod = typeof.GetMethod "cast" + let private asyncCastMethod = resolveCastMethod typeof - let private taskCastMethod = typeof.GetMethod "cast" + let private taskCastMethod = resolveCastMethod typeof /// Per-type cache of the concrete generic MethodInfo produced by MakeGenericMethod. /// Avoids repeated generic-method instantiation for the same return type.