diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index 16b73fca7..a7c9f00bf 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -15,4 +15,5 @@ jobs:
with:
productNamespacePrefix: "Refit"
srcFolder: "./"
- installWorkflows: true
+ secrets:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e8a209595..31459f15d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,7 +14,6 @@ jobs:
configuration: Release
productNamespacePrefix: "Refit"
srcFolder: "./"
- installWorkflows: false
secrets:
SIGN_ACCOUNT_NAME: ${{ secrets.SIGN_ACCOUNT_NAME }}
SIGN_PROFILE_NAME: ${{ secrets.SIGN_PROFILE_NAME }}
diff --git a/Directory.Build.props b/Directory.Build.props
index 8063aebf5..2adbaa5cc 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -23,7 +23,9 @@
true
$(MSBuildThisFileDirectory)buildtask.snk
- net462;netstandard2.0;net8.0;net9.0
+ net462
+ net8.0;net9.0;net10.0
+ $(RefitTargets);netstandard2.0;$(RefitTestTargets)
IDE0040;CA1054;CA1510
diff --git a/InterfaceStubGenerator.Shared/Emitter.cs b/InterfaceStubGenerator.Shared/Emitter.cs
index a83789309..f8364a2fc 100644
--- a/InterfaceStubGenerator.Shared/Emitter.cs
+++ b/InterfaceStubGenerator.Shared/Emitter.cs
@@ -195,7 +195,8 @@ UniqueNameBuilder uniqueNames
),
};
- WriteMethodOpening(source, methodModel, !isTopLevel, isAsync);
+ var isExplicit = methodModel.IsExplicitInterface || !isTopLevel;
+ WriteMethodOpening(source, methodModel, isExplicit, isExplicit, isAsync);
// Build the list of args for the array
var argArray = methodModel
@@ -219,10 +220,18 @@ UniqueNameBuilder uniqueNames
? $", new global::System.Type[] {{ {string.Join(", ", genericArray)} }}"
: string.Empty;
+ // Normalize method lookup key: strip explicit interface prefix if present (e.g. IFoo.Bar -> Bar)
+ var lookupName = methodModel.Name;
+ var lastDotIndex = lookupName.LastIndexOf('.');
+ if (lastDotIndex >= 0 && lastDotIndex < lookupName.Length - 1)
+ {
+ lookupName = lookupName.Substring(lastDotIndex + 1);
+ }
+
source.WriteLine(
$"""
var ______arguments = {argumentsArrayString};
- var ______func = requestBuilder.BuildRestResultFuncForMethod("{methodModel.Name}", {parameterTypesExpression}{genericString} );
+ var ______func = requestBuilder.BuildRestResultFuncForMethod("{lookupName}", {parameterTypesExpression}{genericString} );
{@return}({returnType})______func(this.Client, ______arguments){configureAwait};
"""
@@ -233,7 +242,8 @@ UniqueNameBuilder uniqueNames
private static void WriteNonRefitMethod(SourceWriter source, MethodModel methodModel)
{
- WriteMethodOpening(source, methodModel, true);
+ var isExplicit = methodModel.IsExplicitInterface;
+ WriteMethodOpening(source, methodModel, isExplicit, isExplicit);
source.WriteLine(
@"throw new global::System.NotImplementedException(""Either this method has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument."");"
@@ -242,9 +252,6 @@ private static void WriteNonRefitMethod(SourceWriter source, MethodModel methodM
WriteMethodClosing(source);
}
- // TODO: This assumes that the Dispose method is a void that takes no parameters.
- // The previous version did not.
- // Does the bool overload cause an issue here.
private static void WriteDisposableMethod(SourceWriter source)
{
source.WriteLine(
@@ -293,6 +300,7 @@ UniqueNameBuilder uniqueNames
private static void WriteMethodOpening(
SourceWriter source,
MethodModel methodModel,
+ bool isDerivedExplicitImpl,
bool isExplicitInterface,
bool isAsync = false
)
@@ -308,7 +316,12 @@ private static void WriteMethodOpening(
if (isExplicitInterface)
{
- builder.Append(@$"{methodModel.ContainingType}.");
+ var ct = methodModel.ContainingType;
+ if (!ct.StartsWith("global::"))
+ {
+ ct = "global::" + ct;
+ }
+ builder.Append(@$"{ct}.");
}
builder.Append(@$"{methodModel.DeclaredMethod}(");
@@ -318,7 +331,6 @@ private static void WriteMethodOpening(
foreach (var param in methodModel.Parameters)
{
var annotation = param.Annotation;
-
list.Add($@"{param.Type}{(annotation ? '?' : string.Empty)} @{param.MetadataName}");
}
@@ -330,7 +342,7 @@ private static void WriteMethodOpening(
source.WriteLine();
source.WriteLine(builder.ToString());
source.Indentation++;
- GenerateConstraints(source, methodModel.Constraints, isExplicitInterface);
+ GenerateConstraints(source, methodModel.Constraints, isDerivedExplicitImpl || isExplicitInterface);
source.Indentation--;
source.WriteLine("{");
source.Indentation++;
diff --git a/InterfaceStubGenerator.Shared/ImmutableEquatableArray.cs b/InterfaceStubGenerator.Shared/ImmutableEquatableArray.cs
index 23c1d9009..8b342063c 100644
--- a/InterfaceStubGenerator.Shared/ImmutableEquatableArray.cs
+++ b/InterfaceStubGenerator.Shared/ImmutableEquatableArray.cs
@@ -64,7 +64,7 @@ static int Combine(int h1, int h2)
IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator();
- public struct Enumerator
+ public record struct Enumerator
{
private readonly T[] _values;
private int _index;
diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems
index 92de97af5..e5322e8d8 100644
--- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems
+++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems
@@ -23,6 +23,7 @@
+
diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
index bd6e91cde..3ab4481fa 100644
--- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
+++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
@@ -10,13 +10,8 @@
namespace Refit.Generator
{
- // * Search for all Interfaces, find the method definitions
- // and make sure there's at least one Refit attribute on one
- // * Generate the data we need for the template based on interface method
- // defn's
-
///
- /// InterfaceStubGeneratorV2.
+ /// InterfaceStubGenerator.
///
[Generator]
#if ROSLYN_4
@@ -28,7 +23,6 @@ public class InterfaceStubGenerator : ISourceGenerator
private const string TypeParameterVariableName = "______typeParameters";
#if !ROSLYN_4
-
///
/// Executes the specified context.
///
@@ -51,13 +45,11 @@ out var refitInternalNamespace
context.CancellationToken
);
- // Emit diagnostics
foreach (var diagnostic in parseStep.diagnostics)
{
context.ReportDiagnostic(diagnostic);
}
- // Emit interface stubs
foreach (var interfaceModel in parseStep.contextGenerationSpec.Interfaces)
{
var interfaceText = Emitter.EmitInterface(interfaceModel);
@@ -67,7 +59,6 @@ out var refitInternalNamespace
);
}
- // Emit PreserveAttribute and Generated.Initialize
Emitter.EmitSharedCode(
parseStep.contextGenerationSpec,
(name, code) => context.AddSource(name, code)
@@ -76,15 +67,9 @@ out var refitInternalNamespace
#endif
#if ROSLYN_4
-
- ///
- /// Initializes the specified context.
- ///
- /// The context.
- ///
+ ///
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- // We're looking for methods with an attribute that are in an interface
var candidateMethodsProvider = context.SyntaxProvider.CreateSyntaxProvider(
(syntax, cancellationToken) =>
syntax
@@ -96,8 +81,6 @@ is MethodDeclarationSyntax
(context, cancellationToken) => (MethodDeclarationSyntax)context.Node
);
- // We also look for interfaces that derive from others, so we can see if any base methods contain
- // Refit methods
var candidateInterfacesProvider = context.SyntaxProvider.CreateSyntaxProvider(
(syntax, cancellationToken) =>
syntax is InterfaceDeclarationSyntax { BaseList: not null },
@@ -146,9 +129,6 @@ out var refitInternalNamespace
}
);
- // output the diagnostics
- // use `ImmutableEquatableArray` to cache cases where there are no diagnostics
- // otherwise the subsequent steps will always rerun.
var diagnostics = parseStep
.Select(static (x, _) => x.diagnostics.ToImmutableEquatableArray())
.WithTrackingName(RefitGeneratorStepName.ReportDiagnostics);
@@ -168,14 +148,11 @@ out var refitInternalNamespace
}
);
}
-
#else
-
///
/// Initializes the specified context.
///
/// The context.
- ///
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
@@ -189,7 +166,6 @@ class SyntaxReceiver : ISyntaxReceiver
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
- // We're looking for methods with an attribute that are in an interfaces
if (
syntaxNode is MethodDeclarationSyntax methodDeclarationSyntax
&& methodDeclarationSyntax.Parent is InterfaceDeclarationSyntax
@@ -199,15 +175,12 @@ syntaxNode is MethodDeclarationSyntax methodDeclarationSyntax
CandidateMethods.Add(methodDeclarationSyntax);
}
- // We also look for interfaces that derive from others, so we can see if any base methods contain
- // Refit methods
if (syntaxNode is InterfaceDeclarationSyntax iface && iface.BaseList is not null)
{
CandidateInterfaces.Add(iface);
}
}
}
-
#endif
}
diff --git a/InterfaceStubGenerator.Shared/Models/MethodModel.cs b/InterfaceStubGenerator.Shared/Models/MethodModel.cs
index 513a8b93b..6f6170c10 100644
--- a/InterfaceStubGenerator.Shared/Models/MethodModel.cs
+++ b/InterfaceStubGenerator.Shared/Models/MethodModel.cs
@@ -1,4 +1,6 @@
-namespace Refit.Generator;
+using System.Collections.Immutable;
+
+namespace Refit.Generator;
internal sealed record MethodModel(
string Name,
@@ -7,7 +9,8 @@ internal sealed record MethodModel(
string DeclaredMethod,
ReturnTypeInfo ReturnTypeMetadata,
ImmutableEquatableArray Parameters,
- ImmutableEquatableArray Constraints
+ ImmutableEquatableArray Constraints,
+ bool IsExplicitInterface
);
internal enum ReturnTypeInfo : byte
diff --git a/InterfaceStubGenerator.Shared/Models/WellKnownTypes.cs b/InterfaceStubGenerator.Shared/Models/WellKnownTypes.cs
index 28ea5e403..656e98c63 100644
--- a/InterfaceStubGenerator.Shared/Models/WellKnownTypes.cs
+++ b/InterfaceStubGenerator.Shared/Models/WellKnownTypes.cs
@@ -2,16 +2,41 @@
namespace Refit.Generator;
+///
+/// WellKnownTypes.
+///
public class WellKnownTypes(Compilation compilation)
{
- readonly Dictionary cachedTypes = new();
+ readonly Dictionary cachedTypes = [];
+ ///
+ /// Gets this instance.
+ ///
+ ///
+ ///
public INamedTypeSymbol Get() => Get(typeof(T));
+
+ ///
+ /// Gets the specified type.
+ ///
+ /// The type.
+ ///
+ /// Could not get name of type " + type
public INamedTypeSymbol Get(Type type)
{
+ if (type is null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
+
return Get(type.FullName ?? throw new InvalidOperationException("Could not get name of type " + type));
}
+ ///
+ /// Tries the get.
+ ///
+ /// Full name of the type.
+ ///
public INamedTypeSymbol? TryGet(string typeFullName)
{
if (cachedTypes.TryGetValue(typeFullName, out var typeSymbol))
diff --git a/InterfaceStubGenerator.Shared/Parser.cs b/InterfaceStubGenerator.Shared/Parser.cs
index 280c74d9b..ebd7e6dee 100644
--- a/InterfaceStubGenerator.Shared/Parser.cs
+++ b/InterfaceStubGenerator.Shared/Parser.cs
@@ -309,6 +309,34 @@ bool nullableEnabled
.Cast()
.ToArray();
+ // Exclude base interface methods that the current interface explicitly implements.
+ // This avoids false positive RF001 diagnostics for cases like:
+ // interface IFoo { int Bar(); } and interface IRemoteFoo : IFoo { [Get] abstract int IFoo.Bar(); }
+ if (derivedNonRefitMethods.Length > 0)
+ {
+ var explicitlyImplementedBaseMethods = new HashSet(
+ SymbolEqualityComparer.Default
+ );
+
+ foreach (var member in interfaceSymbol.GetMembers().OfType())
+ {
+ foreach (var baseMethod in member.ExplicitInterfaceImplementations)
+ {
+ // Use OriginalDefinition for robustness when comparing generic methods
+ explicitlyImplementedBaseMethods.Add(
+ baseMethod.OriginalDefinition ?? baseMethod
+ );
+ }
+ }
+
+ if (explicitlyImplementedBaseMethods.Count > 0)
+ {
+ derivedNonRefitMethods = derivedNonRefitMethods
+ .Where(m => !explicitlyImplementedBaseMethods.Contains(m.OriginalDefinition ?? m))
+ .ToArray();
+ }
+ }
+
var memberNames = interfaceSymbol
.GetMembers()
.Select(x => x.Name)
@@ -319,24 +347,39 @@ bool nullableEnabled
var refitMethodsArray = refitMethods
.Select(m => ParseMethod(m, true))
.ToImmutableEquatableArray();
- var derivedRefitMethodsArray = refitMethods
- .Concat(derivedRefitMethods)
+
+ // Only include refit methods discovered on base interfaces here.
+ // Do NOT duplicate the current interface's refit methods.
+ var derivedRefitMethodsArray = derivedRefitMethods
.Select(m => ParseMethod(m, false))
.ToImmutableEquatableArray();
// Handle non-refit Methods that aren't static or properties or have a method body
var nonRefitMethodModelList = new List();
- foreach (var method in nonRefitMethods.Concat(derivedNonRefitMethods))
+ foreach (var method in nonRefitMethods)
{
if (
method.IsStatic
|| method.MethodKind == MethodKind.PropertyGet
|| method.MethodKind == MethodKind.PropertySet
|| !method.IsAbstract
- ) // If an interface method has a body, it won't be abstract
+ )
continue;
- nonRefitMethodModelList.Add(ParseNonRefitMethod(method, diagnostics));
+ nonRefitMethodModelList.Add(ParseNonRefitMethod(method, diagnostics, isDerived: false));
+ }
+ foreach (var method in derivedNonRefitMethods)
+ {
+ if (
+ method.IsStatic
+ || method.MethodKind == MethodKind.PropertyGet
+ || method.MethodKind == MethodKind.PropertySet
+ || !method.IsAbstract
+ )
+ continue;
+
+ // Derived non-refit methods should be emitted as explicit interface implementations
+ nonRefitMethodModelList.Add(ParseNonRefitMethod(method, diagnostics, isDerived: true));
}
var nonRefitMethodModels = nonRefitMethodModelList.ToImmutableEquatableArray();
@@ -369,7 +412,8 @@ bool nullableEnabled
private static MethodModel ParseNonRefitMethod(
IMethodSymbol methodSymbol,
- List diagnostics
+ List diagnostics,
+ bool isDerived
)
{
// report invalid error diagnostic
@@ -384,7 +428,58 @@ List diagnostics
diagnostics.Add(diagnostic);
}
- return ParseMethod(methodSymbol, false);
+ // Parse like a regular method, but force explicit implementation for derived base-interface methods
+ var explicitImpl = methodSymbol.ExplicitInterfaceImplementations.FirstOrDefault();
+ var containingTypeSymbol = explicitImpl?.ContainingType ?? methodSymbol.ContainingType;
+ var containingType = containingTypeSymbol.ToDisplayString(
+ SymbolDisplayFormat.FullyQualifiedFormat
+ );
+
+ // Method name should be simple name only (never include interface qualifier)
+ var declaredBaseName = methodSymbol.Name;
+ var lastDot = declaredBaseName.LastIndexOf('.');
+ if (lastDot >= 0)
+ {
+ declaredBaseName = declaredBaseName.Substring(lastDot + 1);
+ }
+
+ if (methodSymbol.TypeParameters.Length > 0)
+ {
+ var typeParams = string.Join(
+ ", ",
+ methodSymbol.TypeParameters.Select(
+ tp => tp.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
+ )
+ );
+ declaredBaseName += $"<{typeParams}>";
+ }
+
+ var returnType = methodSymbol.ReturnType.ToDisplayString(
+ SymbolDisplayFormat.FullyQualifiedFormat
+ );
+
+ var returnTypeInfo = methodSymbol.ReturnType.MetadataName switch
+ {
+ "Task" => ReturnTypeInfo.AsyncVoid,
+ "Task`1" or "ValueTask`1" => ReturnTypeInfo.AsyncResult,
+ _ => ReturnTypeInfo.Return,
+ };
+
+ var parameters = methodSymbol.Parameters.Select(ParseParameter).ToImmutableEquatableArray();
+
+ var isExplicit = isDerived || explicitImpl is not null;
+ var constraints = GenerateConstraints(methodSymbol.TypeParameters, isExplicit);
+
+ return new MethodModel(
+ methodSymbol.Name,
+ returnType,
+ containingType,
+ declaredBaseName,
+ returnTypeInfo,
+ parameters,
+ constraints,
+ isExplicit
+ );
}
private static bool IsRefitMethod(
@@ -497,10 +592,33 @@ private static MethodModel ParseMethod(IMethodSymbol methodSymbol, bool isImplic
SymbolDisplayFormat.FullyQualifiedFormat
);
- var containingType = methodSymbol.ContainingType.ToDisplayString(
+ // For explicit interface implementations, the containing type for the explicit method signature
+ // must be the interface being implemented (e.g. IFoo), not the interface that declares it.
+ var explicitImpl = methodSymbol.ExplicitInterfaceImplementations.FirstOrDefault();
+ var containingTypeSymbol = explicitImpl?.ContainingType ?? methodSymbol.ContainingType;
+ var containingType = containingTypeSymbol.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat
);
- var declaredMethod = methodSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ // Simple method name (strip any explicit interface qualifier if present)
+ var declaredBaseName = methodSymbol.Name;
+ var lastDot = declaredBaseName.LastIndexOf('.');
+ if (lastDot >= 0)
+ {
+ declaredBaseName = declaredBaseName.Substring(lastDot + 1);
+ }
+
+ if (methodSymbol.TypeParameters.Length > 0)
+ {
+ var typeParams = string.Join(
+ ", ",
+ methodSymbol.TypeParameters.Select(
+ tp => tp.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
+ )
+ );
+ declaredBaseName += $"<{typeParams}>";
+ }
+
var returnTypeInfo = methodSymbol.ReturnType.MetadataName switch
{
"Task" => ReturnTypeInfo.AsyncVoid,
@@ -510,16 +628,18 @@ private static MethodModel ParseMethod(IMethodSymbol methodSymbol, bool isImplic
var parameters = methodSymbol.Parameters.Select(ParseParameter).ToImmutableEquatableArray();
- var constraints = GenerateConstraints(methodSymbol.TypeParameters, !isImplicitInterface);
+ var isExplicit = explicitImpl is not null;
+ var constraints = GenerateConstraints(methodSymbol.TypeParameters, isExplicit || !isImplicitInterface);
return new MethodModel(
methodSymbol.Name,
returnType,
containingType,
- declaredMethod,
+ declaredBaseName,
returnTypeInfo,
parameters,
- constraints
+ constraints,
+ isExplicit
);
}
}
diff --git a/InterfaceStubGenerator.Shared/Polyfills/IndexRange.cs b/InterfaceStubGenerator.Shared/Polyfills/IndexRange.cs
new file mode 100644
index 000000000..15a772f37
--- /dev/null
+++ b/InterfaceStubGenerator.Shared/Polyfills/IndexRange.cs
@@ -0,0 +1,110 @@
+#if NETSTANDARD2_0 || NET462
+namespace System
+{
+ ///
+ /// Minimal polyfill for System.Index to support the C# index syntax when targeting
+ /// .NET Standard 2.0 or .NET Framework 4.6.2. This implementation only exposes the members
+ /// required by this codebase and is not a full replacement for the BCL type.
+ ///
+ ///
+ /// This type exists solely to allow the source to compile on older targets where
+ /// System.Index is not available. It should not be used as a general-purpose
+ /// substitute outside of this project.
+ ///
+ public readonly record struct Index
+ {
+ private readonly int _value;
+ private readonly bool _fromEnd;
+
+ ///
+ /// Creates a new from the start of a sequence.
+ ///
+ /// The zero-based index from the start.
+ public Index(int value) { _value = value; _fromEnd = false; }
+
+ ///
+ /// Creates a new with the specified origin.
+ ///
+ /// The index position value.
+ ///
+ /// When , the index is calculated from the end of the sequence; otherwise from the start.
+ ///
+ public Index(int value, bool fromEnd) { _value = value; _fromEnd = fromEnd; }
+
+ ///
+ /// Gets the index value.
+ ///
+ public int Value => _value;
+
+ ///
+ /// Gets a value indicating whether the index is from the end of the sequence.
+ ///
+ public bool IsFromEnd => _fromEnd;
+
+ ///
+ /// Gets an that points to the start of a sequence.
+ ///
+ public static Index Start => new(0);
+
+ ///
+ /// Gets an that points just past the end of a sequence.
+ ///
+ public static Index End => new(0, true);
+
+ ///
+ /// Implicitly converts an to an from the start.
+ ///
+ /// The zero-based index from the start.
+ public static implicit operator Index(int value) => new(value);
+ }
+
+ ///
+ /// Minimal polyfill for System.Range to support the C# range syntax when targeting
+ /// .NET Standard 2.0 or .NET Framework 4.6.2. This implementation only exposes the members
+ /// required by this codebase and is not a full replacement for the BCL type.
+ ///
+ ///
+ /// This type exists solely to allow the source to compile on older targets where
+ /// System.Range is not available. It should not be used as a general-purpose
+ /// substitute outside of this project.
+ ///
+ public readonly record struct Range
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The inclusive start .
+ /// The exclusive end .
+ public Range(Index start, Index end) { Start = start; End = end; }
+
+ ///
+ /// Gets the inclusive start of the range.
+ ///
+ public Index Start { get; }
+
+ ///
+ /// Gets the exclusive end of the range.
+ ///
+ public Index End { get; }
+
+ ///
+ /// Creates a that starts at the specified index and ends at .
+ ///
+ /// The inclusive start .
+ /// A new .
+ public static Range StartAt(Index start) => new(start, Index.End);
+
+ ///
+ /// Creates a that starts at and ends at the specified index.
+ ///
+ /// The exclusive end .
+ /// A new .
+ public static Range EndAt(Index end) => new(Index.Start, end);
+
+ ///
+ /// Gets a that represents the entire sequence.
+ ///
+ public static Range All => new(Index.Start, Index.End);
+ }
+}
+#endif
diff --git a/Refit.GeneratorTests/Refit.GeneratorTests.csproj b/Refit.GeneratorTests/Refit.GeneratorTests.csproj
index 70d202a36..688333a31 100644
--- a/Refit.GeneratorTests/Refit.GeneratorTests.csproj
+++ b/Refit.GeneratorTests/Refit.GeneratorTests.csproj
@@ -12,20 +12,20 @@
$(NoWarn);CS1591;CA1819;CA2000;CA2007;CA1056;CA1707;CA1861;xUnit1031
-
+
-
+
-
+
-
+
diff --git a/Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs b/Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs
index cf4bd8e1e..13e4e6715 100644
--- a/Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs
+++ b/Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs
@@ -36,15 +36,6 @@ public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient cli
return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
}
-
- ///
- async global::System.Threading.Tasks.Task global::RefitGeneratorTest.IGeneratedClient.Get()
- {
- var ______arguments = global::System.Array.Empty
-
+
-
+
+
+
+
-
+
-
+
diff --git a/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs b/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs
index e1f3523ac..d369011a2 100644
--- a/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs
+++ b/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs
@@ -173,152 +173,6 @@ public RefitTestsIGitHubApi(global::System.Net.Http.HttpClient client, global::R
return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false);
}
-
- private static readonly global::System.Type[] ______typeParameters9 = new global::System.Type[] {typeof(string) };
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.IGitHubApi.GetUser(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUser", ______typeParameters9 );
-
- return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- private static readonly global::System.Type[] ______typeParameters10 = new global::System.Type[] {typeof(string) };
-
- ///
- global::System.IObservable global::Refit.Tests.IGitHubApi.GetUserObservable(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserObservable", ______typeParameters10 );
-
- return (global::System.IObservable)______func(this.Client, ______arguments);
- }
-
- private static readonly global::System.Type[] ______typeParameters11 = new global::System.Type[] {typeof(string) };
-
- ///
- global::System.IObservable global::Refit.Tests.IGitHubApi.GetUserCamelCase(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserCamelCase", ______typeParameters11 );
-
- return (global::System.IObservable)______func(this.Client, ______arguments);
- }
-
- private static readonly global::System.Type[] ______typeParameters12 = new global::System.Type[] {typeof(string), typeof(global::System.Threading.CancellationToken) };
-
- ///
- async global::System.Threading.Tasks.Task> global::Refit.Tests.IGitHubApi.GetOrgMembers(string @orgName, global::System.Threading.CancellationToken @cancellationToken)
- {
- var ______arguments = new object[] { @orgName, @cancellationToken };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetOrgMembers", ______typeParameters12 );
-
- return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- private static readonly global::System.Type[] ______typeParameters13 = new global::System.Type[] {typeof(string) };
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.IGitHubApi.FindUsers(string @q)
- {
- var ______arguments = new object[] { @q };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("FindUsers", ______typeParameters13 );
-
- return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.IGitHubApi.GetIndex()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetIndex", global::System.Array.Empty() );
-
- return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- ///
- global::System.IObservable global::Refit.Tests.IGitHubApi.GetIndexObservable()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetIndexObservable", global::System.Array.Empty() );
-
- return (global::System.IObservable)______func(this.Client, ______arguments);
- }
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.IGitHubApi.NothingToSeeHere()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("NothingToSeeHere", global::System.Array.Empty() );
-
- return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- ///
- async global::System.Threading.Tasks.Task> global::Refit.Tests.IGitHubApi.NothingToSeeHereWithMetadata()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("NothingToSeeHereWithMetadata", global::System.Array.Empty() );
-
- return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- private static readonly global::System.Type[] ______typeParameters14 = new global::System.Type[] {typeof(string) };
-
- ///
- async global::System.Threading.Tasks.Task> global::Refit.Tests.IGitHubApi.GetUserWithMetadata(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserWithMetadata", ______typeParameters14 );
-
- return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- private static readonly global::System.Type[] ______typeParameters15 = new global::System.Type[] {typeof(string) };
-
- ///
- global::System.IObservable> global::Refit.Tests.IGitHubApi.GetUserObservableWithMetadata(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserObservableWithMetadata", ______typeParameters15 );
-
- return (global::System.IObservable>)______func(this.Client, ______arguments);
- }
-
- private static readonly global::System.Type[] ______typeParameters16 = new global::System.Type[] {typeof(string) };
-
- ///
- global::System.IObservable> global::Refit.Tests.IGitHubApi.GetUserIApiResponseObservableWithMetadata(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserIApiResponseObservableWithMetadata", ______typeParameters16 );
-
- return (global::System.IObservable>)______func(this.Client, ______arguments);
- }
-
- private static readonly global::System.Type[] ______typeParameters17 = new global::System.Type[] {typeof(global::Refit.Tests.User) };
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.IGitHubApi.CreateUser(global::Refit.Tests.User @user)
- {
- var ______arguments = new object[] { @user };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("CreateUser", ______typeParameters17 );
-
- return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- private static readonly global::System.Type[] ______typeParameters18 = new global::System.Type[] {typeof(global::Refit.Tests.User) };
-
- ///
- async global::System.Threading.Tasks.Task> global::Refit.Tests.IGitHubApi.CreateUserWithMetadata(global::Refit.Tests.User @user)
- {
- var ______arguments = new object[] { @user };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("CreateUserWithMetadata", ______typeParameters18 );
-
- return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
}
}
}
diff --git a/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApiDisposable.g.verified.cs b/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApiDisposable.g.verified.cs
index 03c579cf4..ae11f238a 100644
--- a/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApiDisposable.g.verified.cs
+++ b/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApiDisposable.g.verified.cs
@@ -37,15 +37,6 @@ public RefitTestsIGitHubApiDisposable(global::System.Net.Http.HttpClient client,
await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
}
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.IGitHubApiDisposable.RefitMethod()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("RefitMethod", global::System.Array.Empty() );
-
- await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
///
void global::System.IDisposable.Dispose()
{
diff --git a/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#INestedGitHubApi.g.verified.cs b/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#INestedGitHubApi.g.verified.cs
index 56f107e8b..622e6b707 100644
--- a/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#INestedGitHubApi.g.verified.cs
+++ b/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#INestedGitHubApi.g.verified.cs
@@ -109,88 +109,6 @@ public RefitTestsTestNestedINestedGitHubApi(global::System.Net.Http.HttpClient c
await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
}
-
- private static readonly global::System.Type[] ______typeParameters4 = new global::System.Type[] {typeof(string) };
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.TestNested.INestedGitHubApi.GetUser(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUser", ______typeParameters4 );
-
- return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- private static readonly global::System.Type[] ______typeParameters5 = new global::System.Type[] {typeof(string) };
-
- ///
- global::System.IObservable global::Refit.Tests.TestNested.INestedGitHubApi.GetUserObservable(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserObservable", ______typeParameters5 );
-
- return (global::System.IObservable)______func(this.Client, ______arguments);
- }
-
- private static readonly global::System.Type[] ______typeParameters6 = new global::System.Type[] {typeof(string) };
-
- ///
- global::System.IObservable global::Refit.Tests.TestNested.INestedGitHubApi.GetUserCamelCase(string @userName)
- {
- var ______arguments = new object[] { @userName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetUserCamelCase", ______typeParameters6 );
-
- return (global::System.IObservable)______func(this.Client, ______arguments);
- }
-
- private static readonly global::System.Type[] ______typeParameters7 = new global::System.Type[] {typeof(string) };
-
- ///
- async global::System.Threading.Tasks.Task> global::Refit.Tests.TestNested.INestedGitHubApi.GetOrgMembers(string @orgName)
- {
- var ______arguments = new object[] { @orgName };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetOrgMembers", ______typeParameters7 );
-
- return await ((global::System.Threading.Tasks.Task>)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- private static readonly global::System.Type[] ______typeParameters8 = new global::System.Type[] {typeof(string) };
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.TestNested.INestedGitHubApi.FindUsers(string @q)
- {
- var ______arguments = new object[] { @q };
- var ______func = requestBuilder.BuildRestResultFuncForMethod("FindUsers", ______typeParameters8 );
-
- return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.TestNested.INestedGitHubApi.GetIndex()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetIndex", global::System.Array.Empty() );
-
- return await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- ///
- global::System.IObservable global::Refit.Tests.TestNested.INestedGitHubApi.GetIndexObservable()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetIndexObservable", global::System.Array.Empty() );
-
- return (global::System.IObservable)______func(this.Client, ______arguments);
- }
-
- ///
- async global::System.Threading.Tasks.Task global::Refit.Tests.TestNested.INestedGitHubApi.NothingToSeeHere()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("NothingToSeeHere", global::System.Array.Empty() );
-
- await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
}
}
}
diff --git a/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#IServiceWithoutNamespace.g.verified.cs b/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#IServiceWithoutNamespace.g.verified.cs
index d4e8fdac4..88fa24c4a 100644
--- a/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#IServiceWithoutNamespace.g.verified.cs
+++ b/Refit.Tests/_snapshots/InterfaceStubGeneratorTests.GenerateInterfaceStubsWithoutNamespaceSmokeTest#IServiceWithoutNamespace.g.verified.cs
@@ -45,24 +45,6 @@ public IServiceWithoutNamespace(global::System.Net.Http.HttpClient client, globa
await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
}
-
- ///
- async global::System.Threading.Tasks.Task global::IServiceWithoutNamespace.GetRoot()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("GetRoot", global::System.Array.Empty() );
-
- await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
-
- ///
- async global::System.Threading.Tasks.Task global::IServiceWithoutNamespace.PostRoot()
- {
- var ______arguments = global::System.Array.Empty();
- var ______func = requestBuilder.BuildRestResultFuncForMethod("PostRoot", global::System.Array.Empty() );
-
- await ((global::System.Threading.Tasks.Task)______func(this.Client, ______arguments)).ConfigureAwait(false);
- }
}
}
}
diff --git a/Refit/AnonymousDisposable.cs b/Refit/AnonymousDisposable.cs
index 1f1215939..029535bcd 100644
--- a/Refit/AnonymousDisposable.cs
+++ b/Refit/AnonymousDisposable.cs
@@ -1,14 +1,7 @@
namespace Refit
{
- sealed class AnonymousDisposable : IDisposable
+ sealed class AnonymousDisposable(Action block) : IDisposable
{
- readonly Action block;
-
- public AnonymousDisposable(Action block)
- {
- this.block = block;
- }
-
public void Dispose()
{
block();
diff --git a/Refit/CloseGenericMethodKey.cs b/Refit/CloseGenericMethodKey.cs
index d9560140f..03bb0c795 100644
--- a/Refit/CloseGenericMethodKey.cs
+++ b/Refit/CloseGenericMethodKey.cs
@@ -2,7 +2,7 @@
namespace Refit
{
- struct CloseGenericMethodKey : IEquatable
+ readonly struct CloseGenericMethodKey : IEquatable
{
internal CloseGenericMethodKey(MethodInfo openMethodInfo, Type[] types)
{
diff --git a/Refit/FormValueMultimap.cs b/Refit/FormValueMultimap.cs
index 72092231c..d305a3c5f 100644
--- a/Refit/FormValueMultimap.cs
+++ b/Refit/FormValueMultimap.cs
@@ -166,9 +166,7 @@ string GetFieldNameForProperty(PropertyInfo propertyInfo)
static PropertyInfo[] GetProperties(Type type)
{
- return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
- .Where(p => p.CanRead && p.GetMethod?.IsPublic == true)
- .ToArray();
+ return [.. type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead && p.GetMethod?.IsPublic == true)];
}
public IEnumerator> GetEnumerator()
diff --git a/Refit/NameValueCollection.cs b/Refit/NameValueCollection.cs
index e313b184b..3a4d13231 100644
--- a/Refit/NameValueCollection.cs
+++ b/Refit/NameValueCollection.cs
@@ -2,6 +2,6 @@
{
class NameValueCollection : Dictionary
{
- public string[] AllKeys => Keys.ToArray();
+ public string[] AllKeys => [.. Keys];
}
}
diff --git a/Refit/Refit.csproj b/Refit/Refit.csproj
index d15b9c9ac..ede96070f 100644
--- a/Refit/Refit.csproj
+++ b/Refit/Refit.csproj
@@ -7,7 +7,7 @@
enable
-
+
true
diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs
index ce7dc63c0..f7fddd4d4 100644
--- a/Refit/RequestBuilderImplementation.cs
+++ b/Refit/RequestBuilderImplementation.cs
@@ -46,7 +46,7 @@ public RequestBuilderImplementation(
TargetType = refitInterfaceType;
- var dict = new Dictionary>();
+ var dict = new Dictionary>(StringComparer.Ordinal);
AddInterfaceHttpMethods(refitInterfaceType, dict);
foreach (var inheritedInterface in targetInterfaceInheritedInterfaces)
@@ -57,12 +57,18 @@ public RequestBuilderImplementation(
interfaceHttpMethods = dict;
}
+ static string GetLookupKeyForMethod(MethodInfo methodInfo)
+ {
+ var name = methodInfo.Name;
+ var lastDot = name.LastIndexOf('.');
+ return lastDot >= 0 ? name.Substring(lastDot + 1) : name;
+ }
+
void AddInterfaceHttpMethods(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type interfaceType,
Dictionary> methods
)
{
- // Consider public (the implicit visibility) and non-public abstract members of the interfaceType
var methodInfos = interfaceType
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Where(i => i.IsAbstract);
@@ -73,10 +79,11 @@ Dictionary> methods
var hasHttpMethod = attrs.OfType().Any();
if (hasHttpMethod)
{
- if (!methods.TryGetValue(methodInfo.Name, out var value))
+ var key = GetLookupKeyForMethod(methodInfo);
+ if (!methods.TryGetValue(key, out var value))
{
value = [];
- methods.Add(methodInfo.Name, value);
+ methods.Add(key, value);
}
var restinfo = new RestMethodInfoInternal(interfaceType, methodInfo, settings);
@@ -116,7 +123,6 @@ RestMethodInfoInternal FindMatchingRestMethodInfo(
method => method.MethodInfo.GetParameters().Length == parameterTypes.Length
);
- // If it's a generic method, add that filter
if (isGeneric)
possibleMethodsCollection = possibleMethodsCollection.Where(
method =>
@@ -124,7 +130,7 @@ RestMethodInfoInternal FindMatchingRestMethodInfo(
&& method.MethodInfo.GetGenericArguments().Length
== genericArgumentTypes!.Length
);
- else // exclude generic methods
+ else
possibleMethodsCollection = possibleMethodsCollection.Where(
method => !method.MethodInfo.IsGenericMethod
);
@@ -187,17 +193,16 @@ RestMethodInfoInternal CloseGenericMethodIfNeeded(
parameterTypes,
genericArgumentTypes
);
+
+ // Task (void)
if (restMethod.ReturnType == typeof(Task))
{
return BuildVoidTaskFuncForMethod(restMethod);
}
- if (restMethod.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
+ // Task
+ if (restMethod.ReturnType.GetTypeInfo().IsGenericType && restMethod.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
{
- // NB: This jacked up reflection code is here because it's
- // difficult to upcast Task to an arbitrary T, especially
- // if you need to AOT everything, so we need to reflectively
- // invoke buildTaskFuncForMethod.
var taskFuncMi = typeof(RequestBuilderImplementation).GetMethod(
nameof(BuildTaskFuncForMethod),
BindingFlags.NonPublic | BindingFlags.Instance
@@ -213,20 +218,48 @@ RestMethodInfoInternal CloseGenericMethodIfNeeded(
return (client, args) => taskFunc!.DynamicInvoke(client, args);
}
- // Same deal
- var rxFuncMi = typeof(RequestBuilderImplementation).GetMethod(
- nameof(BuildRxFuncForMethod),
+ // IObservable
+ if (restMethod.ReturnType.GetTypeInfo().IsGenericType && restMethod.ReturnType.GetGenericTypeDefinition() == typeof(IObservable<>))
+ {
+ var rxFuncMi = typeof(RequestBuilderImplementation).GetMethod(
+ nameof(BuildRxFuncForMethod),
+ BindingFlags.NonPublic | BindingFlags.Instance
+ );
+ var rxFunc = (MulticastDelegate?)
+ (
+ rxFuncMi!.MakeGenericMethod(
+ restMethod.ReturnResultType,
+ restMethod.DeserializedResultType
+ )
+ ).Invoke(this, [restMethod]);
+
+ return (client, args) => rxFunc!.DynamicInvoke(client, args);
+ }
+
+ // Synchronous return types: build a sync wrapper that awaits internally and returns the value
+ var syncFuncMi = typeof(RequestBuilderImplementation).GetMethod(
+ nameof(BuildSyncFuncForMethod),
BindingFlags.NonPublic | BindingFlags.Instance
);
- var rxFunc = (MulticastDelegate?)
+ var syncFunc = (MulticastDelegate?)
(
- rxFuncMi!.MakeGenericMethod(
+ syncFuncMi!.MakeGenericMethod(
restMethod.ReturnResultType,
restMethod.DeserializedResultType
)
).Invoke(this, [restMethod]);
- return (client, args) => rxFunc!.DynamicInvoke(client, args);
+ return (client, args) => syncFunc!.DynamicInvoke(client, args);
+ }
+
+ private Func BuildSyncFuncForMethod(RestMethodInfoInternal restMethod)
+ {
+ var taskFunc = BuildTaskFuncForMethod(restMethod);
+ return (client, paramList) =>
+ {
+ var task = taskFunc(client, paramList);
+ return (object?)task.GetAwaiter().GetResult();
+ };
}
void AddMultipartItem(
diff --git a/Refit/RestMethodInfo.cs b/Refit/RestMethodInfo.cs
index 6273967b2..b87c8248d 100644
--- a/Refit/RestMethodInfo.cs
+++ b/Refit/RestMethodInfo.cs
@@ -672,9 +672,24 @@ void DetermineReturnTypeInfo(MethodInfo methodInfo)
DeserializedResultType = typeof(void);
}
else
- throw new ArgumentException(
- $"Method \"{methodInfo.Name}\" is invalid. All REST Methods must return either Task or ValueTask or IObservable"
- );
+ {
+ // Allow synchronous return types only for non-public or explicit interface members.
+ // This supports internal Refit methods and explicit interface members annotated with Refit attributes.
+ var isExplicitInterfaceMember = methodInfo.Name.IndexOf('.') >= 0;
+ var isNonPublic = !(methodInfo.IsPublic);
+ if (!(isExplicitInterfaceMember || isNonPublic))
+ {
+ throw new ArgumentException(
+ $"Method \"{methodInfo.Name}\" is invalid. All REST Methods must return either Task or ValueTask or IObservable"
+ );
+ }
+
+ ReturnType = methodInfo.ReturnType;
+ ReturnResultType = methodInfo.ReturnType;
+ DeserializedResultType = methodInfo.ReturnType == typeof(IApiResponse)
+ ? typeof(HttpContent)
+ : methodInfo.ReturnType;
+ }
}
void DetermineIfResponseMustBeDisposed()
diff --git a/Refit/RestMethodParameterInfo.cs b/Refit/RestMethodParameterInfo.cs
index dd748e85f..8b384fb15 100644
--- a/Refit/RestMethodParameterInfo.cs
+++ b/Refit/RestMethodParameterInfo.cs
@@ -73,26 +73,20 @@ public RestMethodParameterInfo(bool isObjectPropertyParameter, ParameterInfo par
///
/// RestMethodParameterProperty.
///
- public class RestMethodParameterProperty
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name.
+ /// The property information.
+ public class RestMethodParameterProperty(string name, PropertyInfo propertyInfo)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name.
- /// The property information.
- public RestMethodParameterProperty(string name, PropertyInfo propertyInfo)
- {
- Name = name;
- PropertyInfo = propertyInfo;
- }
-
///
/// Gets or sets the name.
///
///
/// The name.
///
- public string Name { get; set; }
+ public string Name { get; set; } = name;
///
/// Gets or sets the property information.
@@ -100,7 +94,7 @@ public RestMethodParameterProperty(string name, PropertyInfo propertyInfo)
///
/// The property information.
///
- public PropertyInfo PropertyInfo { get; set; }
+ public PropertyInfo PropertyInfo { get; set; } = propertyInfo;
}
///
diff --git a/Refit/SystemTextJsonContentSerializer.cs b/Refit/SystemTextJsonContentSerializer.cs
index 79469f0c9..0152071ab 100644
--- a/Refit/SystemTextJsonContentSerializer.cs
+++ b/Refit/SystemTextJsonContentSerializer.cs
@@ -9,28 +9,18 @@ namespace Refit
///
/// A implementing using the System.Text.Json APIs
///
- public sealed class SystemTextJsonContentSerializer : IHttpContentSerializer
+ ///
+ /// Creates a new instance with the specified parameters
+ ///
+ /// The serialization options to use for the current instance
+ public sealed class SystemTextJsonContentSerializer(JsonSerializerOptions jsonSerializerOptions) : IHttpContentSerializer
{
- ///
- /// The JSON serialization options to use
- ///
- readonly JsonSerializerOptions jsonSerializerOptions;
-
///
/// Creates a new instance
///
public SystemTextJsonContentSerializer()
: this(GetDefaultJsonSerializerOptions()) { }
- ///
- /// Creates a new instance with the specified parameters
- ///
- /// The serialization options to use for the current instance
- public SystemTextJsonContentSerializer(JsonSerializerOptions jsonSerializerOptions)
- {
- this.jsonSerializerOptions = jsonSerializerOptions;
- }
-
///
public HttpContent ToHttpContent(T item)
{