From 85046d8cace696f2285f4fe34710ad7b5fd1514f Mon Sep 17 00:00:00 2001 From: Solo Yolo Date: Thu, 26 Mar 2026 23:33:17 +0900 Subject: [PATCH 1/2] bench: add net9.0 target to Refit.Benchmarks Add net9.0 alongside net8.0 as a benchmark target framework to enable performance measurement on the latest .NET runtime. --- Refit.Benchmarks/Refit.Benchmarks.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refit.Benchmarks/Refit.Benchmarks.csproj b/Refit.Benchmarks/Refit.Benchmarks.csproj index a02d65e65..884a15d19 100644 --- a/Refit.Benchmarks/Refit.Benchmarks.csproj +++ b/Refit.Benchmarks/Refit.Benchmarks.csproj @@ -4,7 +4,7 @@ Exe - net8.0 + net8.0;net9.0 false $(NoWarn);CS1591 From 8d089b1c5d4a37acaf9274803f5d882008675892 Mon Sep 17 00:00:00 2001 From: Solo Yolo Date: Thu, 26 Mar 2026 23:40:45 +0900 Subject: [PATCH 2/2] perf: replace Activator.CreateInstance with direct constructor in ApiResponse.Create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the reflection-based `Activator.CreateInstance(typeof(ApiResponse), ...)` with a direct `new ApiResponse(...)` constructor call, and change the `content` parameter type from `object?` to `TBody?` to eliminate boxing for value types. This removes per-call reflection overhead on every ApiResponse creation, which is the second most common return type pattern in Refit usage (found in 76+ public repos). Benchmark results (BenchmarkDotNet v0.14.0, .NET 9.0.14, AMD Ryzen 7 7700): | Scenario | Before (μs) | After (μs) | Δ Latency | Before (KB) | After (KB) | Δ Alloc | |-----------------------|-------------|------------|-----------|-------------|------------|---------| | OK/Get/STJ | 6.137 | 5.949 | -3.1% | 9.84 | 9.52 | -3.3% | | OK/Get/Newtonsoft | 9.105 | 8.986 | -1.3% | 19.52 | 19.20 | -1.6% | | OK/Post/STJ | 6.732 | 6.669 | -0.9% | 10.89 | 10.56 | -3.0% | | OK/Post/Newtonsoft | 14.204 | 13.814 | -2.7% | 32.03 | 31.72 | -1.0% | | Error/Get/STJ | 2.634 | 2.350 | -10.8% | 12.66 | 12.33 | -2.6% | | Error/Get/Newtonsoft | 2.549 | 2.358 | -7.5% | 12.66 | 12.33 | -2.6% | | Error/Post/STJ | 3.273 | 3.111 | -4.9% | 13.70 | 13.38 | -2.3% | | Error/Post/Newtonsoft | 7.561 | 7.362 | -2.6% | 25.16 | 24.84 | -1.3% | Average: -3.1% latency, -1.9% allocations across all scenarios. Error path sees the largest gains (-10.8%) since the reflection overhead is a larger fraction of total execution time. --- Refit/ApiResponse.cs | 12 ++---------- Refit/RequestBuilderImplementation.cs | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Refit/ApiResponse.cs b/Refit/ApiResponse.cs index 86b485df1..deb0b41bd 100644 --- a/Refit/ApiResponse.cs +++ b/Refit/ApiResponse.cs @@ -10,20 +10,12 @@ static class ApiResponse internal static T Create( HttpRequestMessage request, HttpResponseMessage? resp, - object? content, + TBody? content, RefitSettings settings, ApiExceptionBase? error = null ) { - return (T) - Activator.CreateInstance( - typeof(ApiResponse), - request, - resp, - content, - settings, - error - )!; + return (T)(object)new ApiResponse(request, resp, content, settings, error); } } diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs index 2877f07cb..042ddfae6 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -407,7 +407,7 @@ object itemValue return ApiResponse.Create( rq, resp, - null, + default, settings, new ApiRequestException(rq, rq.Method, settings, ex) );