From 4b87f8d4c45976891c9963b3725a1281da182313 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sat, 19 Jul 2025 00:07:51 +0800 Subject: [PATCH 01/55] fix typo --- .../InterceptorAttributeWithArrayMemberTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/AspectCore.Extensions.DependencyInjection.Test/Issues/InterceptorAttributeWithArrayMemberTests.cs b/tests/AspectCore.Extensions.DependencyInjection.Test/Issues/InterceptorAttributeWithArrayMemberTests.cs index 29267e8d..48c0954f 100644 --- a/tests/AspectCore.Extensions.DependencyInjection.Test/Issues/InterceptorAttributeWithArrayMemberTests.cs +++ b/tests/AspectCore.Extensions.DependencyInjection.Test/Issues/InterceptorAttributeWithArrayMemberTests.cs @@ -29,20 +29,20 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) public interface IUserAppService { - int ExcuteTimes { get; } + int ExecuteTimes { get; } - [Test(Times = new int[] { 10, 100 })] + [Test(Times = new[] { 10, 100 })] string DisplayName(string firstName, string lastName); } public class UserAppService : IUserAppService { - private int _excuteTimes; - public int ExcuteTimes => _excuteTimes; + private int _executeTimes; + public int ExecuteTimes => _executeTimes; public string DisplayName(string firstName, string lastName) { - Interlocked.Increment(ref _excuteTimes); + Interlocked.Increment(ref _executeTimes); var fullName = $"{firstName} {lastName}"; return fullName; } @@ -59,7 +59,7 @@ public void InterceptorAttributeWithArrayMember_Property_Test() var usrAppSrv = sp.GetRequiredService(); var name = usrAppSrv.DisplayName("gain", "loss"); Assert.Equal("gain loss", name); - Assert.Equal(10 + 100, usrAppSrv.ExcuteTimes); + Assert.Equal(10 + 100, usrAppSrv.ExecuteTimes); } } } From 3623e9c3249e2179f18dc1b45e5efdc2520b1817 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sat, 19 Jul 2025 00:32:09 +0800 Subject: [PATCH 02/55] add test cases --- .../DynamicProxy/CovariantReturnTypesTests.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs new file mode 100644 index 00000000..8e4da9ff --- /dev/null +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs @@ -0,0 +1,49 @@ +using AspectCore.DynamicProxy; +using Xunit; + +namespace AspectCore.Tests.DynamicProxy; + +public class CovariantReturnTypesTests : DynamicProxyTestBase +{ + public interface IService + { + object Method(); + object Property { get; } + } + + public class Service : IService + { + public virtual object Method() => nameof(Service); + public virtual object Property { get; } = nameof(Service); + } + + public class CovariantReturnsService : Service + { + public override string Method() => nameof(CovariantReturnsService); + public override string Property { get; } = nameof(CovariantReturnsService); + } + + [Fact] + public void CreateClassProxy_CovariantReturns_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + } + + [Fact] + public void CreateClassProxy_WithServiceType_CovariantReturns_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + } + + [Fact] + public void CreateInterfaceProxy_CovariantReturns_Test() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + } +} From 4d508bd3d82bcdbb948a4b395af2583d16c69092 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sat, 19 Jul 2025 02:03:35 +0800 Subject: [PATCH 03/55] add NewSlot attribute --- src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs | 14 +++++++++++--- .../DynamicProxy/CovariantReturnTypesTests.cs | 7 ++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index 0c635387..c9ca63e7 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -441,17 +441,22 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType if (method.Attributes.HasFlag(MethodAttributes.Public)) { - attributes = attributes | MethodAttributes.Public; + attributes |= MethodAttributes.Public; } if (method.Attributes.HasFlag(MethodAttributes.Family)) { - attributes = attributes | MethodAttributes.Family; + attributes |= MethodAttributes.Family; } if (method.Attributes.HasFlag(MethodAttributes.FamORAssem)) { - attributes = attributes | MethodAttributes.FamORAssem; + attributes |= MethodAttributes.FamORAssem; + } + + if (method.Attributes.HasFlag(MethodAttributes.NewSlot)) + { + attributes |= MethodAttributes.NewSlot; } var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc); @@ -470,6 +475,9 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method //inherit targetMethod's attribute foreach (var customAttributeData in method.CustomAttributes) { + if (customAttributeData.AttributeType.Name == "PreserveBaseOverridesAttribute") + continue; // Skip PreserveBaseOverridesAttribute as it is not needed in dynamic proxy generation. + methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); } diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs index 8e4da9ff..84d88f1f 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs @@ -1,4 +1,5 @@ -using AspectCore.DynamicProxy; +using System.Reflection; +using AspectCore.DynamicProxy; using Xunit; namespace AspectCore.Tests.DynamicProxy; @@ -13,8 +14,8 @@ public interface IService public class Service : IService { - public virtual object Method() => nameof(Service); - public virtual object Property { get; } = nameof(Service); + public virtual object Method() => new(); + public virtual object Property { get; } = new(); } public class CovariantReturnsService : Service From ee52db7cb84f5b3cdf87f538c40248489de0e9a7 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 20 Jul 2025 17:28:48 +0800 Subject: [PATCH 04/55] support interface proxy --- .../Extensions/EnumerableExtensions.cs | 23 +++++++++ .../Extensions/MethodInfoExtensions.cs | 28 +++++++++++ ...oxyGeneratorUtils.CovariantReturnMethod.cs | 42 ++++++++++++++++ .../Utils/ProxyGeneratorUtils.cs | 49 +++++++++++-------- ...Tests.cs => CovariantReturnMethodTests.cs} | 6 +-- 5 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 src/AspectCore.Core/Extensions/EnumerableExtensions.cs create mode 100644 src/AspectCore.Core/Extensions/MethodInfoExtensions.cs create mode 100644 src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs rename tests/AspectCore.Tests/DynamicProxy/{CovariantReturnTypesTests.cs => CovariantReturnMethodTests.cs} (89%) diff --git a/src/AspectCore.Core/Extensions/EnumerableExtensions.cs b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..40ac4fe6 --- /dev/null +++ b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; + +// ReSharper disable once CheckNamespace +namespace AspectCore.Extensions +{ + internal static class EnumerableExtensions + { +#if NETSTANDARD2_0 || NETSTANDARD2_1 + public static IEnumerable<(TFirst First, TSecond Second)> Zip(this IEnumerable first, IEnumerable second) + { + return first.Zip(second, (f, s) => (f, s)); + } +#endif + +#if NETSTANDARD2_0 + public static HashSet ToHashSet(this IEnumerable source, IEqualityComparer comparer = null) + { + return new HashSet(source, comparer); + } +#endif + } +} \ No newline at end of file diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs new file mode 100644 index 00000000..201cfd60 --- /dev/null +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +// ReSharper disable once CheckNamespace +namespace AspectCore.Extensions +{ + internal static class MethodInfoExtensions + { + public static IEnumerable GetInterfaceDeclarationsForMethod(this MethodInfo method) + { + var typeInfo = method.ReflectedType?.GetTypeInfo(); + if (typeInfo is null) + yield break; + + foreach (var implementedInterface in typeInfo.ImplementedInterfaces) + { + var map = typeInfo.GetInterfaceMap(implementedInterface); + foreach (var (interfaceMethod, targetMethod) in map.InterfaceMethods.Zip(map.TargetMethods)) + { + if (targetMethod == method) + yield return interfaceMethod; + } + } + } + } +} + diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs new file mode 100644 index 00000000..ab90639e --- /dev/null +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using AspectCore.Extensions; + +// ReSharper disable once CheckNamespace +namespace AspectCore.Utils +{ + // NOTE: + // For class proxy: We just define the covariant return methods in the implementation type like normal methods, the CLR will handle the propagation. + // For interface proxy: We need to use the covariant return methods as the interface methods' implementation. + internal partial class ProxyGeneratorUtils + { + private static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); + + // key: covariant return method + // value: overridden method's interface declarations + internal static IReadOnlyDictionary> GetCovariantReturnMethodMap(Type implType) + { + var result = new Dictionary>(); + // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. + if (PreserveBaseOverridesAttribute is null) + return result; + + const MethodAttributes attributes = MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot; + var covariantReturnMethods = implType + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(m => (m.Attributes & attributes) == attributes) + .Where(m => m.IsDefined(PreserveBaseOverridesAttribute)) + .ToHashSet(); + + foreach (var method in covariantReturnMethods) + { + var interfaceDeclarations = method.GetInterfaceDeclarationsForMethod().ToHashSet(); + result[method] = interfaceDeclarations; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index c9ca63e7..443c11f5 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -14,7 +14,7 @@ namespace AspectCore.Utils { - internal class ProxyGeneratorUtils + internal partial class ProxyGeneratorUtils { private const string ProxyNameSpace = "AspectCore.DynamicGenerated"; private const string ProxyAssemblyName = "AspectCore.DynamicProxy.Generator"; @@ -392,15 +392,18 @@ internal static MethodBuilder DefineInterfaceImplMethod(MethodInfo method, TypeB internal static void DefineInterfaceProxyMethods(Type interfaceType, Type targetType, Type[] additionalInterfaces, TypeDesc typeDesc) { + var covariantReturnMethodMap = GetCovariantReturnMethodMap(targetType); foreach (var method in interfaceType.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - DefineInterfaceMethod(method, targetType, typeDesc); + var covariantReturnMethod = covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(method)).Key; + DefineInterfaceMethod(method, targetType, typeDesc, covariantReturnMethod); } foreach (var item in additionalInterfaces) { foreach (var method in item.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - DefineExplicitMethod(method, targetType, typeDesc); + var covariantReturnMethod = covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(method)).Key; + DefineExplicitMethod(method, targetType, typeDesc, covariantReturnMethod); } } } @@ -421,16 +424,16 @@ internal static void DefineClassProxyMethods(Type serviceType, Type implType, Ty } } - internal static MethodBuilder DefineInterfaceMethod(MethodInfo method, Type implType, TypeDesc typeDesc) + internal static MethodBuilder DefineInterfaceMethod(MethodInfo method, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) { - var methodBuilder = DefineMethod(method, method.Name, InterfaceMethodAttributes, implType, typeDesc); + var methodBuilder = DefineMethod(method, method.Name, InterfaceMethodAttributes, implType, typeDesc, covariantReturnMethod); typeDesc.Builder.DefineMethodOverride(methodBuilder, method); return methodBuilder; } - internal static MethodBuilder DefineExplicitMethod(MethodInfo method, Type implType, TypeDesc typeDesc) + internal static MethodBuilder DefineExplicitMethod(MethodInfo method, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) { - var methodBuilder = DefineMethod(method, method.GetName(), ExplicitMethodAttributes, implType, typeDesc); + var methodBuilder = DefineMethod(method, method.GetName(), ExplicitMethodAttributes, implType, typeDesc, covariantReturnMethod); typeDesc.Builder.DefineMethodOverride(methodBuilder, method); return methodBuilder; } @@ -454,6 +457,7 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType attributes |= MethodAttributes.FamORAssem; } + // NewSlot is required for covariant return types. if (method.Attributes.HasFlag(MethodAttributes.NewSlot)) { attributes |= MethodAttributes.NewSlot; @@ -463,7 +467,7 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType return methodBuilder; } - private static MethodBuilder DefineMethod(MethodInfo method, string name, MethodAttributes attributes, Type implType, TypeDesc typeDesc) + private static MethodBuilder DefineMethod(MethodInfo method, string name, MethodAttributes attributes, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) { var methodBuilder = typeDesc.Builder.DefineMethod(name, attributes, method.CallingConvention, method.ReturnType, method.GetParameterTypes()); @@ -475,16 +479,13 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method //inherit targetMethod's attribute foreach (var customAttributeData in method.CustomAttributes) { - if (customAttributeData.AttributeType.Name == "PreserveBaseOverridesAttribute") - continue; // Skip PreserveBaseOverridesAttribute as it is not needed in dynamic proxy generation. - methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); } //define paramters ParameterBuilderUtils.DefineParameters(method, methodBuilder); - var implementationMethod = implType.GetTypeInfo().GetMethodBySignature(method); + var implementationMethod = covariantReturnMethod ?? implType.GetTypeInfo().GetMethodBySignature(method); if (implementationMethod == null) { var interfaces = implType.GetInterfaces(); @@ -616,7 +617,7 @@ void EmitProxyMethodBody() ilGen.Emit(OpCodes.Callvirt, MethodUtils.CreateAspectActivator); ilGen.Emit(OpCodes.Ldloc, activatorContext); - EmitReturnVaule(ilGen); + EmitReturnValue(ilGen); if (method.ReturnType != typeof(void)) { @@ -707,7 +708,7 @@ void EmitInitializeMetaData(ILGenerator ilGen) } } - void EmitReturnVaule(ILGenerator ilGen) + void EmitReturnValue(ILGenerator ilGen) { if (method.ReturnType == typeof(void)) { @@ -744,17 +745,25 @@ private class PropertyBuilderUtils { public static void DefineInterfaceProxyProperties(Type interfaceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) { + var covariantReturnMethodMap = GetCovariantReturnMethodMap(implType); + foreach (var property in interfaceType.GetTypeInfo().DeclaredProperties) { var builder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc); - DefineInterfacePropertyMethod(builder, property, implType, typeDesc); + var covariantReturnGetter = property.CanRead + ? covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(property.GetMethod)).Key + : null; + DefineInterfacePropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } foreach (var item in additionalInterfaces) { foreach (var property in item.GetTypeInfo().DeclaredProperties) { var builder = DefineInterfaceProxyProperty(property, property.GetDisplayName(), implType, typeDesc); - DefineExplicitPropertyMethod(builder, property, implType, typeDesc); + var covariantReturnGetter = property.CanRead + ? covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(property.GetMethod)).Key + : null; + DefineExplicitPropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } } } @@ -793,11 +802,11 @@ private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, P } } - private static void DefineInterfacePropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc) + private static void DefineInterfacePropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnGetter = null) { if (property.CanRead) { - var method = MethodBuilderUtils.DefineInterfaceMethod(property.GetMethod, implType, typeDesc); + var method = MethodBuilderUtils.DefineInterfaceMethod(property.GetMethod, implType, typeDesc, covariantReturnGetter); propertyBuilder.SetGetMethod(method); } if (property.CanWrite) @@ -807,11 +816,11 @@ private static void DefineInterfacePropertyMethod(PropertyBuilder propertyBuilde } } - private static void DefineExplicitPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc) + private static void DefineExplicitPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnGetter = null) { if (property.CanRead) { - var method = MethodBuilderUtils.DefineExplicitMethod(property.GetMethod, implType, typeDesc); + var method = MethodBuilderUtils.DefineExplicitMethod(property.GetMethod, implType, typeDesc, covariantReturnGetter); propertyBuilder.SetGetMethod(method); } if (property.CanWrite) diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs similarity index 89% rename from tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs rename to tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index 84d88f1f..a38892a8 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -1,10 +1,10 @@ -using System.Reflection; +using System.Linq; using AspectCore.DynamicProxy; using Xunit; namespace AspectCore.Tests.DynamicProxy; -public class CovariantReturnTypesTests : DynamicProxyTestBase +public class CovariantReturnMethodTests : DynamicProxyTestBase { public interface IService { @@ -20,7 +20,7 @@ public class Service : IService public class CovariantReturnsService : Service { - public override string Method() => nameof(CovariantReturnsService); + public sealed override string Method() => nameof(CovariantReturnsService); public override string Property { get; } = nameof(CovariantReturnsService); } From 4e3c5c12abc6280a2f8950980ce60993e5967af3 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 20 Jul 2025 17:32:33 +0800 Subject: [PATCH 05/55] fix comments --- src/AspectCore.Core/Extensions/MethodInfoExtensions.cs | 2 +- .../Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs | 4 ++-- .../DynamicProxy/CovariantReturnMethodTests.cs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs index 201cfd60..99bf1659 100644 --- a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -7,7 +7,7 @@ namespace AspectCore.Extensions { internal static class MethodInfoExtensions { - public static IEnumerable GetInterfaceDeclarationsForMethod(this MethodInfo method) + public static IEnumerable GetInterfaceDeclarations(this MethodInfo method) { var typeInfo = method.ReflectedType?.GetTypeInfo(); if (typeInfo is null) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs index ab90639e..c2e14843 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs @@ -15,7 +15,7 @@ internal partial class ProxyGeneratorUtils private static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); // key: covariant return method - // value: overridden method's interface declarations + // value: interface method declarations internal static IReadOnlyDictionary> GetCovariantReturnMethodMap(Type implType) { var result = new Dictionary>(); @@ -32,7 +32,7 @@ internal static IReadOnlyDictionary> GetCovarian foreach (var method in covariantReturnMethods) { - var interfaceDeclarations = method.GetInterfaceDeclarationsForMethod().ToHashSet(); + var interfaceDeclarations = method.GetInterfaceDeclarations().ToHashSet(); result[method] = interfaceDeclarations; } diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index a38892a8..4575e4cc 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -1,5 +1,4 @@ -using System.Linq; -using AspectCore.DynamicProxy; +using AspectCore.DynamicProxy; using Xunit; namespace AspectCore.Tests.DynamicProxy; From 75bf6056747960b0800be6c8f5497cb187128d4d Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 20 Jul 2025 17:53:41 +0800 Subject: [PATCH 06/55] fix method attribute --- .../Utils/ProxyGeneratorUtils.cs | 14 ++++----- ...ttribute.cs => AsyncIncrementAttribute.cs} | 2 +- .../AsyncInterceptorTests.cs | 28 ++++++++--------- .../RegistryTests.cs | 6 ++-- .../AsyncInterceptorTests.cs | 30 +++++++++---------- 5 files changed, 40 insertions(+), 40 deletions(-) rename tests/AspectCore.Extensions.LightInject.Test/{AsyncIncreamentAttribute.cs => AsyncIncrementAttribute.cs} (93%) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index 443c11f5..f03c4a1a 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -164,16 +164,16 @@ private Type CreateClassProxyInternal(string name, Type serviceType, Type implTy private class ProxyNameUtils { - private readonly Dictionary _indexs = new Dictionary(); + private readonly Dictionary _indexes = new Dictionary(); private readonly Dictionary, string> _indexMaps = new Dictionary, string>(); private string GetProxyTypeIndex(string className, Type serviceType, Type implementationType) { ProxyNameIndex nameIndex; - if (!_indexs.TryGetValue(className, out nameIndex)) + if (!_indexes.TryGetValue(className, out nameIndex)) { nameIndex = new ProxyNameIndex(); - _indexs[className] = nameIndex; + _indexes[className] = nameIndex; } var key = Tuple.Create(serviceType, implementationType); string index; @@ -360,7 +360,7 @@ private class MethodBuilderUtils const MethodAttributes ExplicitMethodAttributes = MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual; internal const MethodAttributes InterfaceMethodAttributes = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual; const MethodAttributes OverrideMethodAttributes = MethodAttributes.HideBySig | MethodAttributes.Virtual; - private static readonly HashSet ignores = new HashSet { "Finalize" }; + private static readonly HashSet _ignores = new HashSet { "Finalize" }; internal static void DefineInterfaceImplMethods(Type[] interfaceTypes, TypeBuilder implTypeBuilder) { @@ -412,7 +412,7 @@ internal static void DefineClassProxyMethods(Type serviceType, Type implType, Ty { foreach (var method in serviceType.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => !x.IsPropertyBinding())) { - if (method.IsVisibleAndVirtual() && !ignores.Contains(method.Name)) + if (method.IsVisibleAndVirtual() && !_ignores.Contains(method.Name)) DefineClassMethod(method, implType, typeDesc); } foreach (var item in additionalInterfaces) @@ -458,7 +458,7 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType } // NewSlot is required for covariant return types. - if (method.Attributes.HasFlag(MethodAttributes.NewSlot)) + if (method.IsDefined(PreserveBaseOverridesAttribute) && method.Attributes.HasFlag(MethodAttributes.NewSlot)) { attributes |= MethodAttributes.NewSlot; } @@ -482,7 +482,7 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); } - //define paramters + //define parameters ParameterBuilderUtils.DefineParameters(method, methodBuilder); var implementationMethod = covariantReturnMethod ?? implType.GetTypeInfo().GetMethodBySignature(method); diff --git a/tests/AspectCore.Extensions.LightInject.Test/AsyncIncreamentAttribute.cs b/tests/AspectCore.Extensions.LightInject.Test/AsyncIncrementAttribute.cs similarity index 93% rename from tests/AspectCore.Extensions.LightInject.Test/AsyncIncreamentAttribute.cs rename to tests/AspectCore.Extensions.LightInject.Test/AsyncIncrementAttribute.cs index 055e270d..f70dae3b 100644 --- a/tests/AspectCore.Extensions.LightInject.Test/AsyncIncreamentAttribute.cs +++ b/tests/AspectCore.Extensions.LightInject.Test/AsyncIncrementAttribute.cs @@ -5,7 +5,7 @@ namespace AspectCoreTest.LightInject { [AttributeUsage(AttributeTargets.Method)] - public class AsyncIncreamentAttribute : AbstractInterceptorAttribute + public class AsyncIncrementAttribute : AbstractInterceptorAttribute { public override async Task Invoke(AspectContext context, AspectDelegate next) { diff --git a/tests/AspectCore.Extensions.LightInject.Test/AsyncInterceptorTests.cs b/tests/AspectCore.Extensions.LightInject.Test/AsyncInterceptorTests.cs index d9e8456f..183f0120 100644 --- a/tests/AspectCore.Extensions.LightInject.Test/AsyncInterceptorTests.cs +++ b/tests/AspectCore.Extensions.LightInject.Test/AsyncInterceptorTests.cs @@ -8,31 +8,31 @@ namespace AspectCoreTest.LightInject { public class AsyncService { - [AsyncIncreament] - public virtual void DonotGet(int num) + [AsyncIncrement] + public virtual void DoNotGet(int num) { } - [AsyncIncreament] - public virtual Task DonotGetAsync(int num) + [AsyncIncrement] + public virtual Task DoNotGetAsync(int num) { return Task.CompletedTask; } - [AsyncIncreament] + [AsyncIncrement] public virtual int Get(int num) { return num; } - [AsyncIncreament] + [AsyncIncrement] public virtual async Task GetAsyncWithTask(int num) { await Task.Delay(100); return num; } - [AsyncIncreament] + [AsyncIncrement] public virtual async ValueTask GetAsyncWithValueTask(int num) { await Task.Delay(100); @@ -59,25 +59,25 @@ private static IServiceContainer CreateContainer() [Theory] [MemberData(nameof(GetNumbers))] - public void TestIncreamentForVoid(int input) + public void TestIncrementForVoid(int input) { var container = CreateContainer(); var service = container.GetInstance(); - service.DonotGet(input); + service.DoNotGet(input); } [Theory] [MemberData(nameof(GetNumbers))] - public async Task TestIncreamentForTask(int input) + public async Task TestIncrementForTask(int input) { var container = CreateContainer(); var service = container.GetInstance(); - await service.DonotGetAsync(input); + await service.DoNotGetAsync(input); } [Theory] [MemberData(nameof(GetNumbers))] - public void TestIncreamentForResult(int input) + public void TestIncrementForResult(int input) { var container = CreateContainer(); var service = container.GetInstance(); @@ -86,7 +86,7 @@ public void TestIncreamentForResult(int input) [Theory] [MemberData(nameof(GetNumbers))] - public async Task TestIncreamentForTaskResult(int input) + public async Task TestIncrementForTaskResult(int input) { var container = CreateContainer(); var service = container.GetInstance(); @@ -95,7 +95,7 @@ public async Task TestIncreamentForTaskResult(int input) [Theory] [MemberData(nameof(GetNumbers))] - public async Task TestIncreamentForValueTaskResult(int input) + public async Task TestIncrementForValueTaskResult(int input) { var container = CreateContainer(); var service = container.GetInstance(); diff --git a/tests/AspectCore.Extensions.LightInject.Test/RegistryTests.cs b/tests/AspectCore.Extensions.LightInject.Test/RegistryTests.cs index dd6f3a5a..47a23b22 100644 --- a/tests/AspectCore.Extensions.LightInject.Test/RegistryTests.cs +++ b/tests/AspectCore.Extensions.LightInject.Test/RegistryTests.cs @@ -15,12 +15,12 @@ public class RegistryTests public interface IService { - [AsyncIncreament] + [AsyncIncrement] int Foo(); } public class Service : IService { - [AsyncIncreament] + [AsyncIncrement] public virtual int Foo() => Result; } public class ServiceWithRef : IService @@ -32,7 +32,7 @@ public ServiceWithRef(IService service) _service = service; } - [AsyncIncreament] + [AsyncIncrement] public virtual int Foo() => _service.Foo(); } diff --git a/tests/AspectCore.Extensions.Windsor.Test/AsyncInterceptorTests.cs b/tests/AspectCore.Extensions.Windsor.Test/AsyncInterceptorTests.cs index 3deff0fe..f7967611 100644 --- a/tests/AspectCore.Extensions.Windsor.Test/AsyncInterceptorTests.cs +++ b/tests/AspectCore.Extensions.Windsor.Test/AsyncInterceptorTests.cs @@ -10,7 +10,7 @@ namespace AspectCoreTest.Windsor { [AttributeUsage(AttributeTargets.Method)] - public class AsyncIncreamentAttribute : AbstractInterceptorAttribute + public class AsyncIncrementAttribute : AbstractInterceptorAttribute { public override async Task Invoke(AspectContext context, AspectDelegate next) { @@ -36,31 +36,31 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) public class AsyncService { - [AsyncIncreament] - public virtual void DonotGet(int num) + [AsyncIncrement] + public virtual void DoNotGet(int num) { } - [AsyncIncreament] - public virtual Task DonotGetAsync(int num) + [AsyncIncrement] + public virtual Task DoNotGetAsync(int num) { return Task.CompletedTask; } - [AsyncIncreament] + [AsyncIncrement] public virtual int Get(int num) { return num; } - [AsyncIncreament] + [AsyncIncrement] public virtual async Task GetAsyncWithTask(int num) { await Task.Delay(100); return num; } - [AsyncIncreament] + [AsyncIncrement] public virtual async ValueTask GetAsyncWithValueTask(int num) { await Task.Delay(100); @@ -86,25 +86,25 @@ private static IWindsorContainer CreateWindsorContainer() [Theory] [MemberData(nameof(GetNumbers))] - public void TestIncreamentForVoid(int input) + public void TestIncrementForVoid(int input) { var container = CreateWindsorContainer(); var service = container.Resolve(); - service.DonotGet(input); + service.DoNotGet(input); } [Theory] [MemberData(nameof(GetNumbers))] - public async Task TestIncreamentForTask(int input) + public async Task TestIncrementForTask(int input) { var container = CreateWindsorContainer(); var service = container.Resolve(); - await service.DonotGetAsync(input); + await service.DoNotGetAsync(input); } [Theory] [MemberData(nameof(GetNumbers))] - public void TestIncreamentForResult(int input) + public void TestIncrementForResult(int input) { var container = CreateWindsorContainer(); var service = container.Resolve(); @@ -113,7 +113,7 @@ public void TestIncreamentForResult(int input) [Theory] [MemberData(nameof(GetNumbers))] - public async Task TestIncreamentForTaskResult(int input) + public async Task TestIncrementForTaskResult(int input) { var container = CreateWindsorContainer(); var service = container.Resolve(); @@ -122,7 +122,7 @@ public async Task TestIncreamentForTaskResult(int input) [Theory] [MemberData(nameof(GetNumbers))] - public async Task TestIncreamentForValueTaskResult(int input) + public async Task TestIncrementForValueTaskResult(int input) { var container = CreateWindsorContainer(); var service = container.Resolve(); From bd11d184a843cc139704bf24b3eda1dadb33d1b6 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 20 Jul 2025 19:42:49 +0800 Subject: [PATCH 07/55] fix test cases --- .../Extensions/EnumerableExtensions.cs | 3 +- .../Extensions/MethodInfoExtensions.cs | 34 ++++++++++++- ...oxyGeneratorUtils.CovariantReturnMethod.cs | 11 ++-- .../Utils/ProxyGeneratorUtils.cs | 51 +++++++++++-------- .../MethodSignature.cs | 10 ++-- .../CovariantReturnMethodTests.cs | 45 ++++++++++++++-- 6 files changed, 115 insertions(+), 39 deletions(-) diff --git a/src/AspectCore.Core/Extensions/EnumerableExtensions.cs b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs index 40ac4fe6..0706d89f 100644 --- a/src/AspectCore.Core/Extensions/EnumerableExtensions.cs +++ b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs @@ -1,8 +1,7 @@ using System.Collections.Generic; -using System.Linq; // ReSharper disable once CheckNamespace -namespace AspectCore.Extensions +namespace System.Linq { internal static class EnumerableExtensions { diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs index 99bf1659..9e4a109c 100644 --- a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -7,6 +8,8 @@ namespace AspectCore.Extensions { internal static class MethodInfoExtensions { + public static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); + public static IEnumerable GetInterfaceDeclarations(this MethodInfo method) { var typeInfo = method.ReflectedType?.GetTypeInfo(); @@ -23,6 +26,35 @@ public static IEnumerable GetInterfaceDeclarations(this MethodInfo m } } } + + public static bool IsOverriden(this MethodInfo method) + { + return method.GetBaseDefinition() != method; + } + + public static bool IsPreserveBaseOverride(this MethodInfo method, bool checkBase) + { + if (PreserveBaseOverridesAttribute is null) + return false; + + var m = method; + while (true) + { + if (m.IsDefined(PreserveBaseOverridesAttribute)) + return true; + + if (checkBase == false) + break; + + var b = m.GetBaseDefinition(); + if (b == m || b == null) + break; + + m = b; + } + + return false; + } } } diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs index c2e14843..793d83b5 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs @@ -12,22 +12,23 @@ namespace AspectCore.Utils // For interface proxy: We need to use the covariant return methods as the interface methods' implementation. internal partial class ProxyGeneratorUtils { - private static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); - // key: covariant return method // value: interface method declarations internal static IReadOnlyDictionary> GetCovariantReturnMethodMap(Type implType) { var result = new Dictionary>(); // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. - if (PreserveBaseOverridesAttribute is null) + if (AspectCore.Extensions.MethodInfoExtensions.PreserveBaseOverridesAttribute is null) return result; + + var methods = implType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .ToHashSet(); + const MethodAttributes attributes = MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot; var covariantReturnMethods = implType .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(m => (m.Attributes & attributes) == attributes) - .Where(m => m.IsDefined(PreserveBaseOverridesAttribute)) + .Where(m => m.IsPreserveBaseOverride(true)) .ToHashSet(); foreach (var method in covariantReturnMethods) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index f03c4a1a..6add3016 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using AspectCore.DynamicProxy; +using AspectCore.Extensions; using AspectCore.Extensions.Reflection; using AspectCore.Extensions.Reflection.Emit; @@ -457,34 +458,12 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType attributes |= MethodAttributes.FamORAssem; } - // NewSlot is required for covariant return types. - if (method.IsDefined(PreserveBaseOverridesAttribute) && method.Attributes.HasFlag(MethodAttributes.NewSlot)) - { - attributes |= MethodAttributes.NewSlot; - } - var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc); return methodBuilder; } private static MethodBuilder DefineMethod(MethodInfo method, string name, MethodAttributes attributes, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) { - var methodBuilder = typeDesc.Builder.DefineMethod(name, attributes, method.CallingConvention, method.ReturnType, method.GetParameterTypes()); - - GenericParameterUtils.DefineGenericParameter(method, methodBuilder); - - //define method attributes - methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(typeof(DynamicallyAttribute))); - - //inherit targetMethod's attribute - foreach (var customAttributeData in method.CustomAttributes) - { - methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); - } - - //define parameters - ParameterBuilderUtils.DefineParameters(method, methodBuilder); - var implementationMethod = covariantReturnMethod ?? implType.GetTypeInfo().GetMethodBySignature(method); if (implementationMethod == null) { @@ -516,6 +495,34 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method } } + // NOTE: both covariant return method and its corresponding overridden method should be defined with NewSlot attribute. + if (method.IsPreserveBaseOverride(true)) + { + // PreserveBaseOverridesAttribute is used to indicate that the method is a covariant return method. + attributes |= MethodAttributes.NewSlot; + } + else if (implementationMethod.Attributes.HasFlag(MethodAttributes.NewSlot) && implementationMethod.IsOverriden()) + { + // an overridden method with NewSlot attribute is a method overriden covariant return method. + attributes |= MethodAttributes.NewSlot; + } + + var methodBuilder = typeDesc.Builder.DefineMethod(name, attributes, method.CallingConvention, method.ReturnType, method.GetParameterTypes()); + + GenericParameterUtils.DefineGenericParameter(method, methodBuilder); + + //define method attributes + methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(typeof(DynamicallyAttribute))); + + //inherit targetMethod's attribute + foreach (var customAttributeData in method.CustomAttributes) + { + methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); + } + + //define parameters + ParameterBuilderUtils.DefineParameters(method, methodBuilder); + if (method.IsNonAspect()) { EmitMethodBody(); diff --git a/src/AspectCore.Extensions.Reflection/MethodSignature.cs b/src/AspectCore.Extensions.Reflection/MethodSignature.cs index 07688452..52d6439b 100644 --- a/src/AspectCore.Extensions.Reflection/MethodSignature.cs +++ b/src/AspectCore.Extensions.Reflection/MethodSignature.cs @@ -63,19 +63,19 @@ private static int GetSignatureCode(Pair pair) if (parameterTypes.Length > 0) { signatureCode = (signatureCode * 397) ^ parameterTypes.Length.GetHashCode(); - foreach (var paramterType in parameterTypes) + foreach (var parameterType in parameterTypes) { - if (paramterType.IsGenericParameter) + if (parameterType.IsGenericParameter) { continue; } - else if (paramterType.GetTypeInfo().IsGenericType) + else if (parameterType.GetTypeInfo().IsGenericType) { - signatureCode = GetSignatureCode(signatureCode, paramterType); + signatureCode = GetSignatureCode(signatureCode, parameterType); } else { - signatureCode = (signatureCode * 397) ^ paramterType.GetHashCode(); + signatureCode = (signatureCode * 397) ^ parameterType.GetHashCode(); } } } diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index 4575e4cc..177c4374 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -19,12 +19,17 @@ public class Service : IService public class CovariantReturnsService : Service { - public sealed override string Method() => nameof(CovariantReturnsService); + public override string Method() => nameof(CovariantReturnsService); public override string Property { get; } = nameof(CovariantReturnsService); } + public class DerivedCovariantReturnsService : CovariantReturnsService + { + public override string Method() => nameof(DerivedCovariantReturnsService); + } + [Fact] - public void CreateClassProxy_CovariantReturns_Test() + public void CreateClassProxy_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Method()); @@ -32,7 +37,15 @@ public void CreateClassProxy_CovariantReturns_Test() } [Fact] - public void CreateClassProxy_WithServiceType_CovariantReturns_Test() + public void CreateClassProxy_DerivedCovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + } + + [Fact] + public void CreateClassProxy_Service_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Method()); @@ -40,10 +53,34 @@ public void CreateClassProxy_WithServiceType_CovariantReturns_Test() } [Fact] - public void CreateInterfaceProxy_CovariantReturns_Test() + public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + } + + [Fact] + public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + } + + [Fact] + public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateInterfaceProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); } + + [Fact] + public void CreateInterfaceProxy_IService_DerivedCovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + } } From 4401de76e614f9e9c53dafe4d78b0e9aee733806 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 20 Jul 2025 19:45:11 +0800 Subject: [PATCH 08/55] remove unused code --- .../Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs index 793d83b5..415eb162 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using AspectCore.Extensions; +using static AspectCore.Extensions.MethodInfoExtensions; // ReSharper disable once CheckNamespace namespace AspectCore.Utils @@ -18,14 +19,9 @@ internal static IReadOnlyDictionary> GetCovarian { var result = new Dictionary>(); // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. - if (AspectCore.Extensions.MethodInfoExtensions.PreserveBaseOverridesAttribute is null) + if (PreserveBaseOverridesAttribute is null) return result; - - var methods = implType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .ToHashSet(); - - const MethodAttributes attributes = MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot; var covariantReturnMethods = implType .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(m => m.IsPreserveBaseOverride(true)) From a92c66507ee909b6f219e00ee4dc2ca827c91789 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 20 Jul 2025 20:03:08 +0800 Subject: [PATCH 09/55] fix comment --- src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index 6add3016..5b13da2c 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -503,7 +503,7 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method } else if (implementationMethod.Attributes.HasFlag(MethodAttributes.NewSlot) && implementationMethod.IsOverriden()) { - // an overridden method with NewSlot attribute is a method overriden covariant return method. + // an overridden method with NewSlot attribute is a method overriden by a covariant return method. attributes |= MethodAttributes.NewSlot; } From 19354eb01222da706bc55314d286eb1472e750f2 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 20 Jul 2025 21:40:51 +0800 Subject: [PATCH 10/55] merge files --- ...oxyGeneratorUtils.CovariantReturnMethod.cs | 39 ------------------- .../Utils/ProxyGeneratorUtils.cs | 29 +++++++++++++- .../CovariantReturnMethodTests.cs | 2 +- 3 files changed, 29 insertions(+), 41 deletions(-) delete mode 100644 src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs deleted file mode 100644 index 415eb162..00000000 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.CovariantReturnMethod.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using AspectCore.Extensions; -using static AspectCore.Extensions.MethodInfoExtensions; - -// ReSharper disable once CheckNamespace -namespace AspectCore.Utils -{ - // NOTE: - // For class proxy: We just define the covariant return methods in the implementation type like normal methods, the CLR will handle the propagation. - // For interface proxy: We need to use the covariant return methods as the interface methods' implementation. - internal partial class ProxyGeneratorUtils - { - // key: covariant return method - // value: interface method declarations - internal static IReadOnlyDictionary> GetCovariantReturnMethodMap(Type implType) - { - var result = new Dictionary>(); - // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. - if (PreserveBaseOverridesAttribute is null) - return result; - - var covariantReturnMethods = implType - .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(m => m.IsPreserveBaseOverride(true)) - .ToHashSet(); - - foreach (var method in covariantReturnMethods) - { - var interfaceDeclarations = method.GetInterfaceDeclarations().ToHashSet(); - result[method] = interfaceDeclarations; - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index 5b13da2c..4d4d7493 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using AspectCore.DynamicProxy; @@ -163,6 +164,29 @@ private Type CreateClassProxyInternal(string name, Type serviceType, Type implTy return typeDesc.Compile(); } + // key: covariant return method + // value: interface method declarations + internal static IReadOnlyDictionary> GetCovariantReturnMethodMap(Type implType) + { + var result = new Dictionary>(); + // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. + if (AspectCore.Extensions.MethodInfoExtensions.PreserveBaseOverridesAttribute is null) + return result; + + var covariantReturnMethods = implType + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(m => m.IsPreserveBaseOverride(true)) + .ToHashSet(); + + foreach (var method in covariantReturnMethods) + { + var interfaceDeclarations = method.GetInterfaceDeclarations().ToHashSet(); + result[method] = interfaceDeclarations; + } + + return result; + } + private class ProxyNameUtils { private readonly Dictionary _indexes = new Dictionary(); @@ -461,7 +485,10 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc); return methodBuilder; } - + + // NOTE: when a covariant return method is handling: + // For class proxy: We just define the covariant return methods in the implementation type like normal methods, the CLR will handle the propagation. (in this case covariantReturnMethod is null) + // For interface proxy: We need to use the covariant return methods as the interface methods' implementation. (in this case covariantReturnMethod is not null) private static MethodBuilder DefineMethod(MethodInfo method, string name, MethodAttributes attributes, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) { var implementationMethod = covariantReturnMethod ?? implType.GetTypeInfo().GetMethodBySignature(method); diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index 177c4374..0c00096d 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -67,7 +67,7 @@ public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsServ Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); } - + [Fact] public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() { From 28b9a4e838260046457fcacb46b5aa35a9e98058 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 20 Jul 2025 21:44:06 +0800 Subject: [PATCH 11/55] cleanup --- src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index 4d4d7493..98a28b11 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using AspectCore.DynamicProxy; @@ -16,7 +14,7 @@ namespace AspectCore.Utils { - internal partial class ProxyGeneratorUtils + internal class ProxyGeneratorUtils { private const string ProxyNameSpace = "AspectCore.DynamicGenerated"; private const string ProxyAssemblyName = "AspectCore.DynamicProxy.Generator"; From 4e9b707c6ea99abc6514b1a20a98712bcb2eb8fe Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Mon, 21 Jul 2025 07:03:30 +0800 Subject: [PATCH 12/55] add some test cases --- .../Extensions/CollectionExtensions.cs | 15 +++ .../Extensions/MethodInfoExtensions.cs | 25 ++++- .../Extensions/TypeExtensions.cs | 98 +++++++++++++++++ .../Utils/ProxyGeneratorUtils.cs | 104 +++++++++--------- .../CovariantReturnMethodTests.cs | 76 +++++++++++-- 5 files changed, 250 insertions(+), 68 deletions(-) create mode 100644 src/AspectCore.Core/Extensions/CollectionExtensions.cs create mode 100644 src/AspectCore.Core/Extensions/TypeExtensions.cs diff --git a/src/AspectCore.Core/Extensions/CollectionExtensions.cs b/src/AspectCore.Core/Extensions/CollectionExtensions.cs new file mode 100644 index 00000000..cd6d1249 --- /dev/null +++ b/src/AspectCore.Core/Extensions/CollectionExtensions.cs @@ -0,0 +1,15 @@ +// ReSharper disable once CheckNamespace +namespace System.Collections.Generic +{ + internal static class CollectionExtensions + { +#if NETSTANDARD2_0 + public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue) + { + return dictionary.TryGetValue(key, out var obj) + ? obj + : defaultValue; + } +#endif + } +} diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs index 9e4a109c..465c5243 100644 --- a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -27,11 +27,6 @@ public static IEnumerable GetInterfaceDeclarations(this MethodInfo m } } - public static bool IsOverriden(this MethodInfo method) - { - return method.GetBaseDefinition() != method; - } - public static bool IsPreserveBaseOverride(this MethodInfo method, bool checkBase) { if (PreserveBaseOverridesAttribute is null) @@ -55,6 +50,26 @@ public static bool IsPreserveBaseOverride(this MethodInfo method, bool checkBase return false; } + + public static IEnumerable EnumerateBaseDefinition(this MethodInfo method) + { + var m = method; + while (true) + { + yield return m; + + var b = m.GetBaseDefinition(); + if (b == m || b == null) + yield break; + + m = b; + } + } + + public static bool EqualAnyBaseDefinitionTo(this MethodInfo method, MethodInfo other) + { + return method.EnumerateBaseDefinition().Any(m => m == other); + } } } diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs new file mode 100644 index 00000000..00a581ae --- /dev/null +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +// ReSharper disable once CheckNamespace +namespace AspectCore.Extensions +{ + internal static class TypeExtensions + { + public readonly struct CovariantReturnMethodInfo + { + public readonly MethodInfo CovariantReturnMethod; + public readonly MethodInfo OverridenMethod; + public readonly HashSet InterfaceDeclarations; + + public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo overridenMethod, HashSet interfaceDeclarations) + { + InterfaceDeclarations = interfaceDeclarations; + OverridenMethod = overridenMethod; + CovariantReturnMethod = covariantReturnMethod; + } + } + + public static IReadOnlyList GetCovariantReturnMethods(this Type type) + { + var result = new List(); + // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. + if (MethodInfoExtensions.PreserveBaseOverridesAttribute is null) + return result; + + var methods = type + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .GroupBy(m => m.IsPreserveBaseOverride(true)) + .ToDictionary(m => m.Key, m => m.ToArray()); + + var covariantReturnMethods = methods.GetValueOrDefault(true, Array.Empty()); + var otherMethods = methods.GetValueOrDefault(false, Array.Empty()); + + foreach (var covariantReturnMethod in covariantReturnMethods) + { + var overridenMethod = otherMethods.FirstOrDefault(m => Match(covariantReturnMethod, m)); + if (overridenMethod is null) + continue; + + var interfaceDeclarations = covariantReturnMethod.GetInterfaceDeclarations().ToHashSet(); + result.Add(new CovariantReturnMethodInfo(covariantReturnMethod, overridenMethod, interfaceDeclarations)); + } + + return result; + + bool Match(MethodInfo covariantReturnMethod, MethodInfo other) + { + if (covariantReturnMethod.Name != other.Name) + return false; + + // return types should not be the same. + if (covariantReturnMethod.ReturnType == other.ReturnType) + return false; + + if (other.ReturnType.IsAssignableFrom(covariantReturnMethod.ReturnType) == false) + return false; + + var params1 = covariantReturnMethod.GetParameters(); + var params2 = other.GetParameters(); + + if (params1.Length != params2.Length) + return false; + + foreach (var (p1, p2) in params1.Zip(params2)) + { + if (p1.ParameterType != p2.ParameterType) + return false; + } + + var isGeneric = covariantReturnMethod.IsGenericMethod; + if (isGeneric != other.IsGenericMethod) + return false; + + if (isGeneric) + { + var args1 = covariantReturnMethod.GetGenericArguments(); + var args2 = other.GetGenericArguments(); + if (args1.Length != args2.Length) + return false; + + foreach (var (a1, a2) in args1.Zip(args2)) + { + if (a1 != a2) + return false; + } + } + + return true; + } + } + } +} diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index 98a28b11..3c8116f3 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -162,29 +162,6 @@ private Type CreateClassProxyInternal(string name, Type serviceType, Type implTy return typeDesc.Compile(); } - // key: covariant return method - // value: interface method declarations - internal static IReadOnlyDictionary> GetCovariantReturnMethodMap(Type implType) - { - var result = new Dictionary>(); - // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. - if (AspectCore.Extensions.MethodInfoExtensions.PreserveBaseOverridesAttribute is null) - return result; - - var covariantReturnMethods = implType - .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(m => m.IsPreserveBaseOverride(true)) - .ToHashSet(); - - foreach (var method in covariantReturnMethods) - { - var interfaceDeclarations = method.GetInterfaceDeclarations().ToHashSet(); - result[method] = interfaceDeclarations; - } - - return result; - } - private class ProxyNameUtils { private readonly Dictionary _indexes = new Dictionary(); @@ -415,17 +392,23 @@ internal static MethodBuilder DefineInterfaceImplMethod(MethodInfo method, TypeB internal static void DefineInterfaceProxyMethods(Type interfaceType, Type targetType, Type[] additionalInterfaces, TypeDesc typeDesc) { - var covariantReturnMethodMap = GetCovariantReturnMethodMap(targetType); + var covariantReturnMethodMap = targetType.GetCovariantReturnMethods(); foreach (var method in interfaceType.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var covariantReturnMethod = covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(method)).Key; + var covariantReturnMethod = covariantReturnMethodMap + .FirstOrDefault(m => m.InterfaceDeclarations.Contains(method)) + .CovariantReturnMethod; + DefineInterfaceMethod(method, targetType, typeDesc, covariantReturnMethod); } foreach (var item in additionalInterfaces) { foreach (var method in item.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var covariantReturnMethod = covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(method)).Key; + var covariantReturnMethod = covariantReturnMethodMap + .FirstOrDefault(m => m.InterfaceDeclarations.Contains(method)) + .CovariantReturnMethod; + DefineExplicitMethod(method, targetType, typeDesc, covariantReturnMethod); } } @@ -433,8 +416,12 @@ internal static void DefineInterfaceProxyMethods(Type interfaceType, Type target internal static void DefineClassProxyMethods(Type serviceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) { + var covariantReturnMethodMap = implType.GetCovariantReturnMethods(); foreach (var method in serviceType.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => !x.IsPropertyBinding())) { + if (covariantReturnMethodMap.Any(m => m.OverridenMethod.EqualAnyBaseDefinitionTo(method))) + continue; + if (method.IsVisibleAndVirtual() && !_ignores.Contains(method.Name)) DefineClassMethod(method, implType, typeDesc); } @@ -461,7 +448,7 @@ internal static MethodBuilder DefineExplicitMethod(MethodInfo method, Type implT return methodBuilder; } - internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType, TypeDesc typeDesc) + internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType, TypeDesc typeDesc, bool isNewSlot = false) { var attributes = OverrideMethodAttributes; @@ -480,15 +467,22 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType attributes |= MethodAttributes.FamORAssem; } + if (isNewSlot) + { + attributes |= MethodAttributes.NewSlot; + } + var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc); return methodBuilder; } - + // NOTE: when a covariant return method is handling: // For class proxy: We just define the covariant return methods in the implementation type like normal methods, the CLR will handle the propagation. (in this case covariantReturnMethod is null) // For interface proxy: We need to use the covariant return methods as the interface methods' implementation. (in this case covariantReturnMethod is not null) private static MethodBuilder DefineMethod(MethodInfo method, string name, MethodAttributes attributes, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) { + var methodBuilder = typeDesc.Builder.DefineMethod(name, attributes, method.CallingConvention, method.ReturnType, method.GetParameterTypes()); + var implementationMethod = covariantReturnMethod ?? implType.GetTypeInfo().GetMethodBySignature(method); if (implementationMethod == null) { @@ -520,20 +514,6 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method } } - // NOTE: both covariant return method and its corresponding overridden method should be defined with NewSlot attribute. - if (method.IsPreserveBaseOverride(true)) - { - // PreserveBaseOverridesAttribute is used to indicate that the method is a covariant return method. - attributes |= MethodAttributes.NewSlot; - } - else if (implementationMethod.Attributes.HasFlag(MethodAttributes.NewSlot) && implementationMethod.IsOverriden()) - { - // an overridden method with NewSlot attribute is a method overriden by a covariant return method. - attributes |= MethodAttributes.NewSlot; - } - - var methodBuilder = typeDesc.Builder.DefineMethod(name, attributes, method.CallingConvention, method.ReturnType, method.GetParameterTypes()); - GenericParameterUtils.DefineGenericParameter(method, methodBuilder); //define method attributes @@ -542,6 +522,9 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method //inherit targetMethod's attribute foreach (var customAttributeData in method.CustomAttributes) { + if (customAttributeData.AttributeType == AspectCore.Extensions.MethodInfoExtensions.PreserveBaseOverridesAttribute) + continue; + methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); } @@ -777,13 +760,13 @@ private class PropertyBuilderUtils { public static void DefineInterfaceProxyProperties(Type interfaceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) { - var covariantReturnMethodMap = GetCovariantReturnMethodMap(implType); + var covariantReturnMethodMap = implType.GetCovariantReturnMethods(); foreach (var property in interfaceType.GetTypeInfo().DeclaredProperties) { var builder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc); var covariantReturnGetter = property.CanRead - ? covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(property.GetMethod)).Key + ? covariantReturnMethodMap.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod : null; DefineInterfacePropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } @@ -793,7 +776,7 @@ public static void DefineInterfaceProxyProperties(Type interfaceType, Type implT { var builder = DefineInterfaceProxyProperty(property, property.GetDisplayName(), implType, typeDesc); var covariantReturnGetter = property.CanRead - ? covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(property.GetMethod)).Key + ? covariantReturnMethodMap.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod : null; DefineExplicitPropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } @@ -802,12 +785,31 @@ public static void DefineInterfaceProxyProperties(Type interfaceType, Type implT internal static void DefineClassProxyProperties(Type serviceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) { + var covariantReturnMethodMap = implType.GetCovariantReturnMethods(); + foreach (var property in serviceType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { + var isNewSlot = false; + + if (property.CanRead) + { + // skip if the property is overridden by a covariant return method + if (covariantReturnMethodMap.Any(m => m.CovariantReturnMethod.EqualAnyBaseDefinitionTo(property.GetMethod) + && m.OverridenMethod.ReturnType == property.PropertyType)) + continue; + + if (covariantReturnMethodMap.Any(m => m.CovariantReturnMethod.EqualAnyBaseDefinitionTo(property.GetMethod) + && m.CovariantReturnMethod.ReturnType == property.PropertyType)) + { + // this property's getter is a covariant return method. + isNewSlot = true; + } + } + if (property.IsVisibleAndVirtual()) { var builder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc); - DefineClassPropertyMethod(builder, property, implType, typeDesc); + DefineClassPropertyMethod(builder, property, implType, typeDesc, isNewSlot); } } foreach (var item in additionalInterfaces) @@ -820,11 +822,11 @@ internal static void DefineClassProxyProperties(Type serviceType, Type implType, } } - private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc) + private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc, bool isNewSlot = false) { if (property.CanRead) { - var method = MethodBuilderUtils.DefineClassMethod(property.GetMethod, implType, typeDesc); + var method = MethodBuilderUtils.DefineClassMethod(property.GetMethod, implType, typeDesc, isNewSlot); propertyBuilder.SetGetMethod(method); } if (property.CanWrite) @@ -1133,13 +1135,13 @@ internal static void DefineGenericParameter(Type targetType, TypeBuilder typeBui } } - internal static void DefineGenericParameter(MethodInfo tergetMethod, MethodBuilder methodBuilder) + internal static void DefineGenericParameter(MethodInfo targetMethod, MethodBuilder methodBuilder) { - if (!tergetMethod.IsGenericMethod) + if (!targetMethod.IsGenericMethod) { return; } - var genericArguments = tergetMethod.GetGenericArguments().Select(t => t.GetTypeInfo()).ToArray(); + var genericArguments = targetMethod.GetGenericArguments().Select(t => t.GetTypeInfo()).ToArray(); var genericArgumentsBuilders = methodBuilder.DefineGenericParameters(genericArguments.Select(a => a.Name).ToArray()); for (var index = 0; index < genericArguments.Length; index++) { diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index 0c00096d..f7f41300 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -1,86 +1,138 @@ -using AspectCore.DynamicProxy; +using System.Linq; +using System.Threading.Tasks; +using AspectCore.DynamicProxy; using Xunit; namespace AspectCore.Tests.DynamicProxy; public class CovariantReturnMethodTests : DynamicProxyTestBase { + public class Interceptor : AbstractInterceptorAttribute + { + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + await context.Invoke(next); + + var returnType = context.ImplementationMethod.ReturnType; + if (returnType == typeof(string)) + { + context.ReturnValue += nameof(Interceptor); + } + else if (returnType == typeof(object)) + { + context.ReturnValue = nameof(Interceptor); + } + } + } + public interface IService { - object Method(); object Property { get; } + object Method(); + + [Interceptor] + object ProxyMethod(); } public class Service : IService { - public virtual object Method() => new(); - public virtual object Property { get; } = new(); + public virtual object Property { get; } = 1; + public virtual object Method() => 1; + + [Interceptor] + public virtual object ProxyMethod() => new(); } public class CovariantReturnsService : Service { - public override string Method() => nameof(CovariantReturnsService); public override string Property { get; } = nameof(CovariantReturnsService); + public override string Method() => nameof(CovariantReturnsService); + + [Interceptor] + public override string ProxyMethod() => nameof(CovariantReturnsService); } public class DerivedCovariantReturnsService : CovariantReturnsService { public override string Method() => nameof(DerivedCovariantReturnsService); + + [Interceptor] + public override string ProxyMethod() => nameof(DerivedCovariantReturnsService); + } + + [Fact] + public void CreateClassProxy_Service_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(1, service.Property); + Assert.Equal(1, service.Method()); + Assert.Equal(nameof(Interceptor), service.ProxyMethod()); } [Fact] public void CreateClassProxy_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } [Fact] public void CreateClassProxy_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } [Fact] public void CreateClassProxy_Service_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } [Fact] public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } [Fact] public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } [Fact] public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } [Fact] public void CreateInterfaceProxy_IService_DerivedCovariantReturnsService_Test() { + var methods = typeof(DerivedCovariantReturnsService).GetMethods() + .Where(m => m.Name == nameof(IService.ProxyMethod)) + .ToArray(); + var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } } From a83d93fdcc3007a3142ff6992ef032b31d2772ff Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 22 Jul 2025 01:19:38 +0800 Subject: [PATCH 13/55] fix some cases --- .../Extensions/MethodInfoExtensions.cs | 38 +------- .../Properties/AssemblyInfo.cs | 2 + .../Utils/ProxyGeneratorUtils.cs | 96 +++++++++---------- .../CovariantReturnMethodTests.cs | 36 +++---- 4 files changed, 66 insertions(+), 106 deletions(-) diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs index 465c5243..ba79a0b2 100644 --- a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -32,43 +32,15 @@ public static bool IsPreserveBaseOverride(this MethodInfo method, bool checkBase if (PreserveBaseOverridesAttribute is null) return false; - var m = method; - while (true) - { - if (m.IsDefined(PreserveBaseOverridesAttribute)) - return true; - - if (checkBase == false) - break; - - var b = m.GetBaseDefinition(); - if (b == m || b == null) - break; - - m = b; - } - - return false; - } - - public static IEnumerable EnumerateBaseDefinition(this MethodInfo method) - { - var m = method; - while (true) - { - yield return m; - - var b = m.GetBaseDefinition(); - if (b == m || b == null) - yield break; + if (method.IsDefined(PreserveBaseOverridesAttribute)) + return true; - m = b; - } + return checkBase && method.GetBaseDefinition().IsDefined(PreserveBaseOverridesAttribute); } - public static bool EqualAnyBaseDefinitionTo(this MethodInfo method, MethodInfo other) + public static bool IsSameBaseDefinition(this MethodInfo method, MethodInfo other) { - return method.EnumerateBaseDefinition().Any(m => m == other); + return method.GetBaseDefinition() == other.GetBaseDefinition(); } } } diff --git a/src/AspectCore.Core/Properties/AssemblyInfo.cs b/src/AspectCore.Core/Properties/AssemblyInfo.cs index 28b81503..b2f77286 100644 --- a/src/AspectCore.Core/Properties/AssemblyInfo.cs +++ b/src/AspectCore.Core/Properties/AssemblyInfo.cs @@ -28,3 +28,5 @@ "e15b6849fbabea83fc9b8b6abf959e606f5e51b268a6a6c2d4757bbc3ae33689373faaedf61077" + "59678c9b")] #endif + +[assembly: InternalsVisibleTo("AspectCore.Tests")] diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index 3c8116f3..e5acd3cf 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -392,10 +392,10 @@ internal static MethodBuilder DefineInterfaceImplMethod(MethodInfo method, TypeB internal static void DefineInterfaceProxyMethods(Type interfaceType, Type targetType, Type[] additionalInterfaces, TypeDesc typeDesc) { - var covariantReturnMethodMap = targetType.GetCovariantReturnMethods(); + var covariantReturnMethods = targetType.GetCovariantReturnMethods(); foreach (var method in interfaceType.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var covariantReturnMethod = covariantReturnMethodMap + var covariantReturnMethod = covariantReturnMethods .FirstOrDefault(m => m.InterfaceDeclarations.Contains(method)) .CovariantReturnMethod; @@ -405,7 +405,7 @@ internal static void DefineInterfaceProxyMethods(Type interfaceType, Type target { foreach (var method in item.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var covariantReturnMethod = covariantReturnMethodMap + var covariantReturnMethod = covariantReturnMethods .FirstOrDefault(m => m.InterfaceDeclarations.Contains(method)) .CovariantReturnMethod; @@ -416,14 +416,28 @@ internal static void DefineInterfaceProxyMethods(Type interfaceType, Type target internal static void DefineClassProxyMethods(Type serviceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) { - var covariantReturnMethodMap = implType.GetCovariantReturnMethods(); + var covariantReturnMethods = implType.GetCovariantReturnMethods(); foreach (var method in serviceType.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => !x.IsPropertyBinding())) { - if (covariantReturnMethodMap.Any(m => m.OverridenMethod.EqualAnyBaseDefinitionTo(method))) - continue; - if (method.IsVisibleAndVirtual() && !_ignores.Contains(method.Name)) + { + var covariantReturnMethod = covariantReturnMethods.FirstOrDefault(m => m.OverridenMethod.IsSameBaseDefinition(method)); + var overriden = covariantReturnMethod.OverridenMethod; + if (overriden != null) + { + // if method is the base definition of the overriden method, the CovariantReturnMethod is not in serviceType, so we need to add CovariantReturnMethod to implType. + // otherwise, the CovariantReturnMethod is also in serviceType, which will be added to implType next for-loops. + if (overriden.GetBaseDefinition() == method) + { + DefineClassMethod(covariantReturnMethod.CovariantReturnMethod, implType, typeDesc); + } + + // covariantReturnMethod is found, do not add method to implType. + continue; + } + DefineClassMethod(method, implType, typeDesc); + } } foreach (var item in additionalInterfaces) { @@ -448,7 +462,7 @@ internal static MethodBuilder DefineExplicitMethod(MethodInfo method, Type implT return methodBuilder; } - internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType, TypeDesc typeDesc, bool isNewSlot = false) + internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null, bool isNewSlot = false) { var attributes = OverrideMethodAttributes; @@ -472,17 +486,31 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType attributes |= MethodAttributes.NewSlot; } - var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc); + var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc, covariantReturnMethod); return methodBuilder; } - // NOTE: when a covariant return method is handling: - // For class proxy: We just define the covariant return methods in the implementation type like normal methods, the CLR will handle the propagation. (in this case covariantReturnMethod is null) - // For interface proxy: We need to use the covariant return methods as the interface methods' implementation. (in this case covariantReturnMethod is not null) private static MethodBuilder DefineMethod(MethodInfo method, string name, MethodAttributes attributes, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) { var methodBuilder = typeDesc.Builder.DefineMethod(name, attributes, method.CallingConvention, method.ReturnType, method.GetParameterTypes()); + GenericParameterUtils.DefineGenericParameter(method, methodBuilder); + + //define method attributes + methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(typeof(DynamicallyAttribute))); + + //inherit targetMethod's attribute + foreach (var customAttributeData in method.CustomAttributes) + { + if (customAttributeData.AttributeType == AspectCore.Extensions.MethodInfoExtensions.PreserveBaseOverridesAttribute) + continue; + + methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); + } + + //define parameters + ParameterBuilderUtils.DefineParameters(method, methodBuilder); + var implementationMethod = covariantReturnMethod ?? implType.GetTypeInfo().GetMethodBySignature(method); if (implementationMethod == null) { @@ -514,23 +542,6 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method } } - GenericParameterUtils.DefineGenericParameter(method, methodBuilder); - - //define method attributes - methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(typeof(DynamicallyAttribute))); - - //inherit targetMethod's attribute - foreach (var customAttributeData in method.CustomAttributes) - { - if (customAttributeData.AttributeType == AspectCore.Extensions.MethodInfoExtensions.PreserveBaseOverridesAttribute) - continue; - - methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); - } - - //define parameters - ParameterBuilderUtils.DefineParameters(method, methodBuilder); - if (method.IsNonAspect()) { EmitMethodBody(); @@ -760,13 +771,13 @@ private class PropertyBuilderUtils { public static void DefineInterfaceProxyProperties(Type interfaceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) { - var covariantReturnMethodMap = implType.GetCovariantReturnMethods(); + var covariantReturnMethods = implType.GetCovariantReturnMethods(); foreach (var property in interfaceType.GetTypeInfo().DeclaredProperties) { var builder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc); var covariantReturnGetter = property.CanRead - ? covariantReturnMethodMap.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod + ? covariantReturnMethods.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod : null; DefineInterfacePropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } @@ -776,7 +787,7 @@ public static void DefineInterfaceProxyProperties(Type interfaceType, Type implT { var builder = DefineInterfaceProxyProperty(property, property.GetDisplayName(), implType, typeDesc); var covariantReturnGetter = property.CanRead - ? covariantReturnMethodMap.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod + ? covariantReturnMethods.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod : null; DefineExplicitPropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } @@ -785,31 +796,20 @@ public static void DefineInterfaceProxyProperties(Type interfaceType, Type implT internal static void DefineClassProxyProperties(Type serviceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) { - var covariantReturnMethodMap = implType.GetCovariantReturnMethods(); + var covariantReturnMethods = implType.GetCovariantReturnMethods(); foreach (var property in serviceType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - var isNewSlot = false; + MethodInfo covariantReturnGetter = null; if (property.CanRead) { - // skip if the property is overridden by a covariant return method - if (covariantReturnMethodMap.Any(m => m.CovariantReturnMethod.EqualAnyBaseDefinitionTo(property.GetMethod) - && m.OverridenMethod.ReturnType == property.PropertyType)) - continue; - - if (covariantReturnMethodMap.Any(m => m.CovariantReturnMethod.EqualAnyBaseDefinitionTo(property.GetMethod) - && m.CovariantReturnMethod.ReturnType == property.PropertyType)) - { - // this property's getter is a covariant return method. - isNewSlot = true; - } } if (property.IsVisibleAndVirtual()) { var builder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc); - DefineClassPropertyMethod(builder, property, implType, typeDesc, isNewSlot); + DefineClassPropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } } foreach (var item in additionalInterfaces) @@ -822,11 +822,11 @@ internal static void DefineClassProxyProperties(Type serviceType, Type implType, } } - private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc, bool isNewSlot = false) + private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnGetter = null) { if (property.CanRead) { - var method = MethodBuilderUtils.DefineClassMethod(property.GetMethod, implType, typeDesc, isNewSlot); + var method = MethodBuilderUtils.DefineClassMethod(property.GetMethod, implType, typeDesc, covariantReturnGetter); propertyBuilder.SetGetMethod(method); } if (property.CanWrite) diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index f7f41300..cca2b395 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -1,5 +1,4 @@ -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using AspectCore.DynamicProxy; using Xunit; @@ -27,7 +26,7 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) public interface IService { - object Property { get; } + //object Property { get; } object Method(); [Interceptor] @@ -36,7 +35,7 @@ public interface IService public class Service : IService { - public virtual object Property { get; } = 1; + //public virtual object Property { get; } = 1; public virtual object Method() => 1; [Interceptor] @@ -45,7 +44,7 @@ public class Service : IService public class CovariantReturnsService : Service { - public override string Property { get; } = nameof(CovariantReturnsService); + //public override string Property { get; } = nameof(CovariantReturnsService); public override string Method() => nameof(CovariantReturnsService); [Interceptor] @@ -60,20 +59,11 @@ public class DerivedCovariantReturnsService : CovariantReturnsService public override string ProxyMethod() => nameof(DerivedCovariantReturnsService); } - [Fact] - public void CreateClassProxy_Service_Test() - { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(1, service.Property); - Assert.Equal(1, service.Method()); - Assert.Equal(nameof(Interceptor), service.ProxyMethod()); - } - [Fact] public void CreateClassProxy_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); + //Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -82,7 +72,7 @@ public void CreateClassProxy_CovariantReturnsService_Test() public void CreateClassProxy_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); + //Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -91,7 +81,7 @@ public void CreateClassProxy_DerivedCovariantReturnsService_Test() public void CreateClassProxy_Service_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); + //Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -100,7 +90,7 @@ public void CreateClassProxy_Service_CovariantReturnsService_Test() public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); + //Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -109,7 +99,7 @@ public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); + //Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -118,7 +108,7 @@ public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsServ public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); + //Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -126,12 +116,8 @@ public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() [Fact] public void CreateInterfaceProxy_IService_DerivedCovariantReturnsService_Test() { - var methods = typeof(DerivedCovariantReturnsService).GetMethods() - .Where(m => m.Name == nameof(IService.ProxyMethod)) - .ToArray(); - var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); + //Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } From ca54e21093cefd3387e2ae1cbf5c9ea90c9e169a Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 22 Jul 2025 01:22:40 +0800 Subject: [PATCH 14/55] fix comment --- src/AspectCore.Core/Properties/AssemblyInfo.cs | 2 -- src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/AspectCore.Core/Properties/AssemblyInfo.cs b/src/AspectCore.Core/Properties/AssemblyInfo.cs index b2f77286..28b81503 100644 --- a/src/AspectCore.Core/Properties/AssemblyInfo.cs +++ b/src/AspectCore.Core/Properties/AssemblyInfo.cs @@ -28,5 +28,3 @@ "e15b6849fbabea83fc9b8b6abf959e606f5e51b268a6a6c2d4757bbc3ae33689373faaedf61077" + "59678c9b")] #endif - -[assembly: InternalsVisibleTo("AspectCore.Tests")] diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index e5acd3cf..a6bd57b1 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -426,7 +426,7 @@ internal static void DefineClassProxyMethods(Type serviceType, Type implType, Ty if (overriden != null) { // if method is the base definition of the overriden method, the CovariantReturnMethod is not in serviceType, so we need to add CovariantReturnMethod to implType. - // otherwise, the CovariantReturnMethod is also in serviceType, which will be added to implType next for-loops. + // otherwise, the CovariantReturnMethod is also in serviceType, which will be added to implType in next for-loops. if (overriden.GetBaseDefinition() == method) { DefineClassMethod(covariantReturnMethod.CovariantReturnMethod, implType, typeDesc); From 5ee9105951f7c7a42cc3c87bc5977d4842f2880d Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 22 Jul 2025 01:23:30 +0800 Subject: [PATCH 15/55] revert some code --- src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index a6bd57b1..cbabdacb 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -462,7 +462,7 @@ internal static MethodBuilder DefineExplicitMethod(MethodInfo method, Type implT return methodBuilder; } - internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null, bool isNewSlot = false) + internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) { var attributes = OverrideMethodAttributes; @@ -481,11 +481,6 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType attributes |= MethodAttributes.FamORAssem; } - if (isNewSlot) - { - attributes |= MethodAttributes.NewSlot; - } - var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc, covariantReturnMethod); return methodBuilder; } From e640c153005deaa230b3f3531d814a2b9c2c1fe8 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 22 Jul 2025 07:46:00 +0800 Subject: [PATCH 16/55] fix some cases --- .../Extensions/MethodInfoExtensions.cs | 6 +- .../Extensions/TypeExtensions.cs | 31 ++++++---- .../Utils/ProxyGeneratorUtils.cs | 57 +++++++++++++++---- .../CovariantReturnMethodTests.cs | 37 ++++++------ 4 files changed, 89 insertions(+), 42 deletions(-) diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs index ba79a0b2..dbb62c4e 100644 --- a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -1,15 +1,13 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection; +using static AspectCore.Extensions.TypeExtensions; // ReSharper disable once CheckNamespace namespace AspectCore.Extensions { internal static class MethodInfoExtensions { - public static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); - public static IEnumerable GetInterfaceDeclarations(this MethodInfo method) { var typeInfo = method.ReflectedType?.GetTypeInfo(); diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 00a581ae..8d9a4790 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -6,27 +6,34 @@ // ReSharper disable once CheckNamespace namespace AspectCore.Extensions { - internal static class TypeExtensions + internal readonly struct CovariantReturnMethodInfo { - public readonly struct CovariantReturnMethodInfo + public readonly MethodInfo CovariantReturnMethod; + public readonly MethodInfo OverridenMethod; + public readonly HashSet InterfaceDeclarations; + + public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo overridenMethod, HashSet interfaceDeclarations) { - public readonly MethodInfo CovariantReturnMethod; - public readonly MethodInfo OverridenMethod; - public readonly HashSet InterfaceDeclarations; + InterfaceDeclarations = interfaceDeclarations; + OverridenMethod = overridenMethod; + CovariantReturnMethod = covariantReturnMethod; + } + } - public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo overridenMethod, HashSet interfaceDeclarations) - { - InterfaceDeclarations = interfaceDeclarations; - OverridenMethod = overridenMethod; - CovariantReturnMethod = covariantReturnMethod; - } + internal static class TypeExtensions + { + public static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); + + public static bool IsPreserveBaseOverrides(this Type type) + { + return type == PreserveBaseOverridesAttribute; } public static IReadOnlyList GetCovariantReturnMethods(this Type type) { var result = new List(); // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. - if (MethodInfoExtensions.PreserveBaseOverridesAttribute is null) + if (PreserveBaseOverridesAttribute is null) return result; var methods = type diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index cbabdacb..2378e114 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -5,12 +5,15 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.InteropServices.ComTypes; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; using AspectCore.DynamicProxy; using AspectCore.Extensions; using AspectCore.Extensions.Reflection; using AspectCore.Extensions.Reflection.Emit; +using TypeExtensions = AspectCore.Extensions.TypeExtensions; namespace AspectCore.Utils { @@ -497,7 +500,7 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method //inherit targetMethod's attribute foreach (var customAttributeData in method.CustomAttributes) { - if (customAttributeData.AttributeType == AspectCore.Extensions.MethodInfoExtensions.PreserveBaseOverridesAttribute) + if (customAttributeData.AttributeType.IsPreserveBaseOverrides()) continue; methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); @@ -795,18 +798,34 @@ internal static void DefineClassProxyProperties(Type serviceType, Type implType, foreach (var property in serviceType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - MethodInfo covariantReturnGetter = null; - - if (property.CanRead) - { - } - if (property.IsVisibleAndVirtual()) { + // covariant return property can only have getter. + if (property.CanRead && property.CanWrite == false) + { + var covariantReturn = covariantReturnMethods.FirstOrDefault(m => IsOverriddenByCovariantReturnProperty(property, m)); + var overriden = covariantReturn.OverridenMethod; + if (overriden != null) + { + // if method is the base definition of the overriden method, the CovariantReturnMethod is not in serviceType, so we need to add CovariantReturnMethod to implType. + // otherwise, the CovariantReturnMethod is also in serviceType, which will be added to implType in next for-loops. + if (overriden.GetBaseDefinition() == property.GetMethod) + { + var propertyBuilder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc); + var method = MethodBuilderUtils.DefineClassMethod(covariantReturn.CovariantReturnMethod, implType, typeDesc); + propertyBuilder.SetGetMethod(method); + } + + // covariant return property is found, do not add property to implType. + continue; + } + } + var builder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc); - DefineClassPropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); + DefineClassPropertyMethod(builder, property, implType, typeDesc); } } + foreach (var item in additionalInterfaces) { foreach (var property in item.GetTypeInfo().DeclaredProperties) @@ -815,13 +834,31 @@ internal static void DefineClassProxyProperties(Type serviceType, Type implType, DefineExplicitPropertyMethod(builder, property, implType, typeDesc); } } + + bool IsOverriddenByCovariantReturnProperty(PropertyInfo property, CovariantReturnMethodInfo info) + { + // this case occurs when the property is not overridden in the serviceType. + var get = property.GetMethod; + if (info.OverridenMethod.IsSameBaseDefinition(get)) + return true; + + // this case occurs when the property is overridden in the serviceType. + // in this case, the property type is super class of (and not the same as) the getter's type. + var covariantReturn = info.CovariantReturnMethod; + if (covariantReturn.IsSameBaseDefinition(get) + && covariantReturn.ReturnType != property.PropertyType + && property.PropertyType.IsAssignableFrom(covariantReturn.ReturnType)) + return true; + + return false; + } } - private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnGetter = null) + private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc) { if (property.CanRead) { - var method = MethodBuilderUtils.DefineClassMethod(property.GetMethod, implType, typeDesc, covariantReturnGetter); + var method = MethodBuilderUtils.DefineClassMethod(property.GetMethod, implType, typeDesc); propertyBuilder.SetGetMethod(method); } if (property.CanWrite) diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index cca2b395..3cd662d3 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -26,7 +26,7 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) public interface IService { - //object Property { get; } + object Property { get; } object Method(); [Interceptor] @@ -35,7 +35,7 @@ public interface IService public class Service : IService { - //public virtual object Property { get; } = 1; + public virtual object Property { get; } = 1; public virtual object Method() => 1; [Interceptor] @@ -44,7 +44,7 @@ public class Service : IService public class CovariantReturnsService : Service { - //public override string Property { get; } = nameof(CovariantReturnsService); + public override string Property { get; } = nameof(CovariantReturnsService); public override string Method() => nameof(CovariantReturnsService); [Interceptor] @@ -53,17 +53,20 @@ public class CovariantReturnsService : Service public class DerivedCovariantReturnsService : CovariantReturnsService { - public override string Method() => nameof(DerivedCovariantReturnsService); - [Interceptor] public override string ProxyMethod() => nameof(DerivedCovariantReturnsService); } + public class DerivedCovariantReturnsService2 : DerivedCovariantReturnsService + { + public override string Property { get; } = nameof(CovariantReturnsService); + } + [Fact] public void CreateClassProxy_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - //Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -71,9 +74,11 @@ public void CreateClassProxy_CovariantReturnsService_Test() [Fact] public void CreateClassProxy_DerivedCovariantReturnsService_Test() { + return; // 会死锁, 待修. DefineClassPropertyMethod + var service = ProxyGenerator.CreateClassProxy(); - //Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -81,7 +86,7 @@ public void CreateClassProxy_DerivedCovariantReturnsService_Test() public void CreateClassProxy_Service_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - //Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -90,8 +95,8 @@ public void CreateClassProxy_Service_CovariantReturnsService_Test() public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - //Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -99,8 +104,8 @@ public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - //Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -108,7 +113,7 @@ public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsServ public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateInterfaceProxy(); - //Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -117,8 +122,8 @@ public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() public void CreateInterfaceProxy_IService_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateInterfaceProxy(); - //Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(DerivedCovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } } From 2caca2f77999affd226dfa9e72017dfcd6d6a61d Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 22 Jul 2025 07:52:29 +0800 Subject: [PATCH 17/55] cleanup --- src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index 2378e114..e3cd0a14 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -5,15 +5,12 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; -using System.Runtime.InteropServices.ComTypes; using System.Threading; using System.Threading.Tasks; -using System.Xml.Linq; using AspectCore.DynamicProxy; using AspectCore.Extensions; using AspectCore.Extensions.Reflection; using AspectCore.Extensions.Reflection.Emit; -using TypeExtensions = AspectCore.Extensions.TypeExtensions; namespace AspectCore.Utils { @@ -465,7 +462,7 @@ internal static MethodBuilder DefineExplicitMethod(MethodInfo method, Type implT return methodBuilder; } - internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null) + internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType, TypeDesc typeDesc) { var attributes = OverrideMethodAttributes; @@ -484,7 +481,7 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType attributes |= MethodAttributes.FamORAssem; } - var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc, covariantReturnMethod); + var methodBuilder = DefineMethod(method, method.Name, attributes, implType, typeDesc); return methodBuilder; } From 5d607cba334404bcc7a251390f7d424675441d13 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 22 Jul 2025 22:53:39 +0800 Subject: [PATCH 18/55] fix a test case --- .../Extensions/TypeExtensions.cs | 5 ----- .../Utils/ProxyGeneratorUtils.cs | 16 ++++++++++------ src/AspectCore.Core/Utils/ReflectionUtils.cs | 2 +- .../DynamicProxy/CovariantReturnMethodTests.cs | 18 +++++++++++------- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 8d9a4790..8512cd33 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -24,11 +24,6 @@ internal static class TypeExtensions { public static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); - public static bool IsPreserveBaseOverrides(this Type type) - { - return type == PreserveBaseOverridesAttribute; - } - public static IReadOnlyList GetCovariantReturnMethods(this Type type) { var result = new List(); diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index e3cd0a14..efd72c20 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -497,9 +497,6 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method //inherit targetMethod's attribute foreach (var customAttributeData in method.CustomAttributes) { - if (customAttributeData.AttributeType.IsPreserveBaseOverrides()) - continue; - methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData)); } @@ -857,6 +854,13 @@ private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, P { var method = MethodBuilderUtils.DefineClassMethod(property.GetMethod, implType, typeDesc); propertyBuilder.SetGetMethod(method); + + if (property.GetMethod.IsPreserveBaseOverride(true)) + { + // property.GetMethod is a covariant return type method, we need to define an override for it. + // otherwise, the TypeDesc.Compile() will run forever. + typeDesc.Builder.DefineMethodOverride(method, property.GetMethod); + } } if (property.CanWrite) { @@ -990,10 +994,10 @@ public static void DefineParameters(MethodInfo targetMethod, MethodBuilder metho } } - var returnParamter = targetMethod.ReturnParameter; - var returnParameterBuilder = methodBuilder.DefineParameter(0, returnParamter.Attributes, returnParamter.Name); + var returnParameter = targetMethod.ReturnParameter; + var returnParameterBuilder = methodBuilder.DefineParameter(0, returnParameter.Attributes, returnParameter.Name); returnParameterBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(typeof(DynamicallyAttribute))); - foreach (var attribute in returnParamter.CustomAttributes) + foreach (var attribute in returnParameter.CustomAttributes) { returnParameterBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(attribute)); } diff --git a/src/AspectCore.Core/Utils/ReflectionUtils.cs b/src/AspectCore.Core/Utils/ReflectionUtils.cs index b4a67aae..ec13b0a8 100644 --- a/src/AspectCore.Core/Utils/ReflectionUtils.cs +++ b/src/AspectCore.Core/Utils/ReflectionUtils.cs @@ -168,7 +168,7 @@ public static bool IsVisibleAndVirtual(this PropertyInfo property) throw new ArgumentNullException(nameof(property)); } return (property.CanRead && property.GetMethod.IsVisibleAndVirtual()) || - (property.CanWrite && property.GetMethod.IsVisibleAndVirtual()); + (property.CanWrite && property.SetMethod.IsVisibleAndVirtual()); } public static bool IsVisibleAndVirtual(this MethodInfo method) diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index 3cd662d3..8ba5ca11 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -29,6 +29,7 @@ public interface IService object Property { get; } object Method(); + object ProxyProperty { [Interceptor] get; } [Interceptor] object ProxyMethod(); } @@ -38,6 +39,7 @@ public class Service : IService public virtual object Property { get; } = 1; public virtual object Method() => 1; + public virtual object ProxyProperty { [Interceptor] get; } = new(); [Interceptor] public virtual object ProxyMethod() => new(); } @@ -47,38 +49,35 @@ public class CovariantReturnsService : Service public override string Property { get; } = nameof(CovariantReturnsService); public override string Method() => nameof(CovariantReturnsService); + public override string ProxyProperty { [Interceptor] get; } = nameof(CovariantReturnsService); [Interceptor] public override string ProxyMethod() => nameof(CovariantReturnsService); } public class DerivedCovariantReturnsService : CovariantReturnsService { + public override string ProxyProperty { [Interceptor] get; } = nameof(DerivedCovariantReturnsService); [Interceptor] public override string ProxyMethod() => nameof(DerivedCovariantReturnsService); } - public class DerivedCovariantReturnsService2 : DerivedCovariantReturnsService - { - public override string Property { get; } = nameof(CovariantReturnsService); - } - [Fact] public void CreateClassProxy_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } [Fact] public void CreateClassProxy_DerivedCovariantReturnsService_Test() { - return; // 会死锁, 待修. DefineClassPropertyMethod - var service = ProxyGenerator.CreateClassProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -88,6 +87,7 @@ public void CreateClassProxy_Service_CovariantReturnsService_Test() var service = ProxyGenerator.CreateClassProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -97,6 +97,7 @@ public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() var service = ProxyGenerator.CreateClassProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -106,6 +107,7 @@ public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsServ var service = ProxyGenerator.CreateClassProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -115,6 +117,7 @@ public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() var service = ProxyGenerator.CreateInterfaceProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } @@ -124,6 +127,7 @@ public void CreateInterfaceProxy_IService_DerivedCovariantReturnsService_Test() var service = ProxyGenerator.CreateInterfaceProxy(); Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); } } From 38311afa8e39fec837131e99da9453f2ab4d3b00 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 22 Jul 2025 23:39:58 +0800 Subject: [PATCH 19/55] refine code --- .../Utils/ProxyGeneratorUtils.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs index efd72c20..ca3dc6a6 100644 --- a/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs +++ b/src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs @@ -395,23 +395,24 @@ internal static void DefineInterfaceProxyMethods(Type interfaceType, Type target var covariantReturnMethods = targetType.GetCovariantReturnMethods(); foreach (var method in interfaceType.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var covariantReturnMethod = covariantReturnMethods - .FirstOrDefault(m => m.InterfaceDeclarations.Contains(method)) - .CovariantReturnMethod; - + var covariantReturnMethod = GetCovariantReturnMethod(method); DefineInterfaceMethod(method, targetType, typeDesc, covariantReturnMethod); } foreach (var item in additionalInterfaces) { foreach (var method in item.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var covariantReturnMethod = covariantReturnMethods - .FirstOrDefault(m => m.InterfaceDeclarations.Contains(method)) - .CovariantReturnMethod; - + var covariantReturnMethod = GetCovariantReturnMethod(method); DefineExplicitMethod(method, targetType, typeDesc, covariantReturnMethod); } } + + MethodInfo GetCovariantReturnMethod(MethodInfo interfaceMethod) + { + return covariantReturnMethods + .FirstOrDefault(m => m.InterfaceDeclarations.Contains(interfaceMethod)) + .CovariantReturnMethod; + } } internal static void DefineClassProxyMethods(Type serviceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) @@ -421,15 +422,15 @@ internal static void DefineClassProxyMethods(Type serviceType, Type implType, Ty { if (method.IsVisibleAndVirtual() && !_ignores.Contains(method.Name)) { - var covariantReturnMethod = covariantReturnMethods.FirstOrDefault(m => m.OverridenMethod.IsSameBaseDefinition(method)); - var overriden = covariantReturnMethod.OverridenMethod; + var covariantReturn = covariantReturnMethods.FirstOrDefault(m => m.OverridenMethod.IsSameBaseDefinition(method)); + var overriden = covariantReturn.OverridenMethod; if (overriden != null) { // if method is the base definition of the overriden method, the CovariantReturnMethod is not in serviceType, so we need to add CovariantReturnMethod to implType. // otherwise, the CovariantReturnMethod is also in serviceType, which will be added to implType in next for-loops. if (overriden.GetBaseDefinition() == method) { - DefineClassMethod(covariantReturnMethod.CovariantReturnMethod, implType, typeDesc); + DefineClassMethod(covariantReturn.CovariantReturnMethod, implType, typeDesc); } // covariantReturnMethod is found, do not add method to implType. @@ -767,23 +768,26 @@ public static void DefineInterfaceProxyProperties(Type interfaceType, Type implT foreach (var property in interfaceType.GetTypeInfo().DeclaredProperties) { + var covariantReturnGetter = FindCovariantReturnGetter(property); var builder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc); - var covariantReturnGetter = property.CanRead - ? covariantReturnMethods.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod - : null; DefineInterfacePropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } foreach (var item in additionalInterfaces) { foreach (var property in item.GetTypeInfo().DeclaredProperties) { + var covariantReturnGetter = FindCovariantReturnGetter(property); var builder = DefineInterfaceProxyProperty(property, property.GetDisplayName(), implType, typeDesc); - var covariantReturnGetter = property.CanRead - ? covariantReturnMethods.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod - : null; DefineExplicitPropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter); } } + + MethodInfo FindCovariantReturnGetter(PropertyInfo property) + { + return property.CanRead + ? covariantReturnMethods.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod + : null; + } } internal static void DefineClassProxyProperties(Type serviceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc) From 6730c2e6447c67d3296c382f86b2513d688dd9fa Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Fri, 3 Oct 2025 21:59:13 -0230 Subject: [PATCH 20/55] add some test cases --- .../Extensions/TypeExtensions.cs | 22 +++ .../Properties/AssemblyInfo.cs | 2 + .../Extensions/CovariantReturnTests.cs | 131 ++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 8512cd33..1e4d0bb4 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -8,8 +8,30 @@ namespace AspectCore.Extensions { internal readonly struct CovariantReturnMethodInfo { + /// + /// The method that defines the covariant return type — + /// i.e., the overriding method that returns a more derived type. + /// public readonly MethodInfo CovariantReturnMethod; + + /// + /// Gets the method that is overridden or implemented by . + /// + /// + /// This is **reflected from the derived type**, not necessarily + /// the base definition returned by . + /// + /// In other words, it represents the version of the base or interface method as seen + /// through the derived class’s reflection context, which may differ from the canonical + /// base definition when covariant return types are involved. + /// + /// public readonly MethodInfo OverridenMethod; + + /// + /// The set of interface method declarations (if any) + /// that are implemented by the . + /// public readonly HashSet InterfaceDeclarations; public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo overridenMethod, HashSet interfaceDeclarations) diff --git a/src/AspectCore.Core/Properties/AssemblyInfo.cs b/src/AspectCore.Core/Properties/AssemblyInfo.cs index 28b81503..b2f77286 100644 --- a/src/AspectCore.Core/Properties/AssemblyInfo.cs +++ b/src/AspectCore.Core/Properties/AssemblyInfo.cs @@ -28,3 +28,5 @@ "e15b6849fbabea83fc9b8b6abf959e606f5e51b268a6a6c2d4757bbc3ae33689373faaedf61077" + "59678c9b")] #endif + +[assembly: InternalsVisibleTo("AspectCore.Tests")] diff --git a/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs b/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs new file mode 100644 index 00000000..082dc376 --- /dev/null +++ b/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs @@ -0,0 +1,131 @@ +using System.Linq; +using System.Reflection; +using AspectCore.Extensions; +using Xunit; + +namespace AspectCore.Tests.Extensions; + +public class CovariantReturnTests +{ + // 1️⃣ Basic covariant override between classes + private class Animal { } + + private class Dog : Animal { } + + private class BaseClass + { + public virtual Animal Make() => new(); + } + + private class DerivedClass : BaseClass + { + public override Dog Make() => new(); + } + + [Fact] + public void FindsBasicCovariantReturnMethod() + { + var methods = typeof(DerivedClass).GetCovariantReturnMethods(); + Assert.Single(methods); + + var info = methods.Single(); + Assert.Equal(nameof(DerivedClass.Make), info.CovariantReturnMethod.Name); + Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); + Assert.Equal(typeof(Animal), info.OverridenMethod.ReturnType); + Assert.True(info.OverridenMethod.DeclaringType == typeof(DerivedClass) + || info.OverridenMethod.DeclaringType == typeof(BaseClass)); + } + + // Multi-level inheritance + private class B1 { public virtual B1 Clone() => new(); } + + private class B2 : B1 { public override B2 Clone() => new(); } + + private class B3 : B2 { } + + [Fact] + public void DoesNotReportEmptyForDeeperHierarchy() + { + var methods = typeof(B3).GetCovariantReturnMethods(); + // Should inherit from B2 → B1, so no new covariant method + Assert.Single(methods); + + methods = typeof(B2).GetCovariantReturnMethods(); + Assert.Single(methods); + } + + // Interface with covariant return + private interface IFactory { T Create(); } + + private class Widget { } + + private class FancyWidget : Widget { } + + private class WidgetFactory : IFactory + { + public virtual Widget Create() => new(); + } + + private class FancyWidgetFactory : WidgetFactory, IFactory + { + public override FancyWidget Create() => new(); + } + + [Fact] + public void HandlesInterfaceCovariantReturnCorrectly() + { + var info = typeof(FancyWidgetFactory).GetCovariantReturnMethods().Single(); + Assert.Equal(typeof(FancyWidget), info.CovariantReturnMethod.ReturnType); + Assert.Equal(typeof(Widget), info.OverridenMethod.ReturnType); + // Should list IFactory.Create() as an interface declaration + Assert.Contains(info.InterfaceDeclarations, m => m.DeclaringType!.GetGenericTypeDefinition() == typeof(IFactory<>)); + } + + // Explicit interface implementation + private interface ICreator { T Create(); } + + private class Creator : ICreator + { + Animal ICreator.Create() => new(); + } + + private class DogCreator : Creator, ICreator + { + Dog ICreator.Create() => new(); + } + + [Fact] + public void FindsExplicitInterfaceImplementation() + { + var infos = typeof(DogCreator).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .Where(m => m.Name.Contains("ICreator")) + .SelectMany(m => m.GetInterfaceDeclarations()) + .ToList(); + Assert.NotEmpty(infos); + Assert.All(infos, i => Assert.Equal("Create", i.Name)); + } + + // 5Generic method covariance + private class GenBase { public virtual T Build() => default!; } + + private class GenDerived : GenBase { public override Dog Build() => new(); } + + [Fact] + public void WorksForGenericCovariantReturn() + { + var info = typeof(GenDerived).GetCovariantReturnMethods().Single(); + Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); + Assert.Empty(info.CovariantReturnMethod.GetGenericArguments()); + Assert.Empty(info.OverridenMethod.GetGenericArguments()); + Assert.NotEqual(info.OverridenMethod, info.OverridenMethod.GetBaseDefinition()); + } + + // Same slot verification + [Fact] + public void IsSameBaseDefinition_CovariantReturn() + { + var m1 = typeof(DerivedClass).GetMethod(nameof(DerivedClass.Make))!; + var m2 = typeof(BaseClass).GetMethod(nameof(BaseClass.Make))!; + Assert.False(m1.IsSameBaseDefinition(m2)); + } +} From a3b084f9cac570642b46c7b5d0474865779453ba Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 13 May 2026 15:21:34 -0230 Subject: [PATCH 21/55] re support Covariant return types after the maintainer's refactory --- .../Builders/ClassProxyAstBuilder.cs | 23 ++++++++++++++++++- .../Builders/InterfaceImplAstBuilder.cs | 23 +++++++++++++++++-- .../ProxyBuilder/ProxyTypeCompiler.cs | 6 ++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index a4101c31..e8b9053e 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs @@ -6,6 +6,7 @@ using AspectCore.Utils; using AspectCore.Extensions.Reflection; using AspectCore.DynamicProxy.ProxyBuilder.Nodes; +using AspectCore.Extensions; namespace AspectCore.DynamicProxy.ProxyBuilder.Builders { @@ -125,13 +126,19 @@ private List BuildConstructors() private void BuildClassMethods(List methods, List methodConstants) { + var covariantReturnMethods = _implType.GetCovariantReturnMethods(); + foreach (var method in _serviceType.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(x => !x.IsPropertyBinding())) { if (!method.IsVisibleAndVirtual() || Ignores.Contains(method.Name)) continue; - var implMethod = InterfaceImplBuilder.ResolveImplementationMethod(method, _implType); + var (covariantReturnMethod, skip) = GetCovariantReturnMethod(method); + if (skip) + continue; + + var implMethod = covariantReturnMethod ?? InterfaceImplBuilder.ResolveImplementationMethod(method, _implType); var body = MethodBodyFactory.DecideBody(method, implMethod, _aspectValidator, _serviceType); var attributes = MethodBuilderConstants.OverrideMethodAttributes; @@ -146,6 +153,20 @@ private void BuildClassMethods(List methods, List m.OverridenMethod.IsSameBaseDefinition(method)); + var overriden = covariantReturn.OverridenMethod; + if (overriden == null) + return (null, false); + + // if method is the base definition of the overriden method, the CovariantReturnMethod is not in serviceType, so we need to add CovariantReturnMethod to implType. + // otherwise, the CovariantReturnMethod is also in serviceType, which will be added to implType in next for-loops. + return overriden.GetBaseDefinition() == method + ? (covariantReturn.CovariantReturnMethod, true) + : (null, true); + } } private void BuildClassProperties(List properties, List methods, List methodConstants) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs index cbc824ba..8767cf4a 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs @@ -6,6 +6,7 @@ using AspectCore.Utils; using AspectCore.Extensions.Reflection; using AspectCore.DynamicProxy.ProxyBuilder.Nodes; +using AspectCore.Extensions; namespace AspectCore.DynamicProxy.ProxyBuilder.Builders { @@ -239,11 +240,13 @@ internal static void BuildInterfaceProxyMembers( List methodConstants) { var resolvedImplType = implType ?? interfaceType; + var covariantReturnMethods = resolvedImplType.GetCovariantReturnMethods(); // Primary interface methods foreach (var method in interfaceType.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var implMethod = ResolveImplementationMethod(method, resolvedImplType); + var covariantReturnMethod = FindCovariantReturnMethod(method); + var implMethod = covariantReturnMethod ?? ResolveImplementationMethod(method, resolvedImplType); var body = MethodBodyFactory.DecideBody(method, implMethod, aspectValidator, interfaceType); var node = BuildProxyMethod(method, implMethod, method.Name, MethodBuilderConstants.InterfaceMethodAttributes, body, method, methodConstants); @@ -255,7 +258,8 @@ internal static void BuildInterfaceProxyMembers( { foreach (var method in iface.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var implMethod = ResolveImplementationMethod(method, resolvedImplType); + var covariantReturnMethod = FindCovariantReturnMethod(method); + var implMethod = covariantReturnMethod ?? ResolveImplementationMethod(method, resolvedImplType); var body = MethodBodyFactory.DecideBody(method, implMethod, aspectValidator, interfaceType); var node = BuildProxyMethod(method, implMethod, method.GetName(), MethodBuilderConstants.ExplicitMethodAttributes, body, method, methodConstants); @@ -266,6 +270,7 @@ internal static void BuildInterfaceProxyMembers( // Primary interface properties foreach (var property in interfaceType.GetTypeInfo().DeclaredProperties) { + var covariantReturnGetter = FindCovariantReturnGetter(property); properties.Add(BuildProxyProperty(property, property.Name, resolvedImplType, aspectValidator, interfaceType, MethodBuilderConstants.InterfaceMethodAttributes, methods, methodConstants)); } @@ -279,6 +284,20 @@ internal static void BuildInterfaceProxyMembers( interfaceType, MethodBuilderConstants.ExplicitMethodAttributes, methods, methodConstants)); } } + + MethodInfo FindCovariantReturnMethod(MethodInfo interfaceMethod) + { + return covariantReturnMethods + .FirstOrDefault(m => m.InterfaceDeclarations.Contains(interfaceMethod)) + .CovariantReturnMethod; + } + + MethodInfo FindCovariantReturnGetter(PropertyInfo property) + { + return property.CanRead + ? covariantReturnMethods.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod + : null; + } } private static PropertyNode BuildProxyProperty( diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/ProxyTypeCompiler.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/ProxyTypeCompiler.cs index 4163b0ea..727391bd 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/ProxyTypeCompiler.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/ProxyTypeCompiler.cs @@ -134,16 +134,16 @@ private Type CreateClassProxyInternal(string name, Type serviceType, Type implTy private class ProxyNameUtils { - private readonly Dictionary _indexs = new Dictionary(); + private readonly Dictionary _indexes = new Dictionary(); private readonly Dictionary, string> _indexMaps = new Dictionary, string>(); private string GetProxyTypeIndex(string className, Type serviceType, Type implementationType) { ProxyNameIndex nameIndex; - if (!_indexs.TryGetValue(className, out nameIndex)) + if (!_indexes.TryGetValue(className, out nameIndex)) { nameIndex = new ProxyNameIndex(); - _indexs[className] = nameIndex; + _indexes[className] = nameIndex; } var key = Tuple.Create(serviceType, implementationType); string index; From 987a06c6c5d71d0dc6e0a43a03069ce2beeaaf23 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 13 May 2026 19:07:12 -0230 Subject: [PATCH 22/55] refine code --- .../Builders/ClassProxyAstBuilder.cs | 137 ++++++++++++------ .../Builders/InterfaceImplAstBuilder.cs | 10 +- .../Extensions/MethodInfoExtensions.cs | 50 ++++++- .../Extensions/TypeExtensions.cs | 14 +- .../Properties/AssemblyInfo.cs | 9 ++ .../Extensions/MethodExtensions.cs | 30 ++-- .../CovariantReturnMethodTests.cs | 24 +-- .../Extensions/CovariantReturnTests.cs | 14 +- 8 files changed, 195 insertions(+), 93 deletions(-) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index e8b9053e..e6108d1c 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs @@ -124,6 +124,30 @@ private List BuildConstructors() return result; } + private MethodNode CreateClassProxyMethodNode(MethodInfo method, MethodInfo implMethod, MethodInfo overridesMethod, List methodConstants) + { + var body = MethodBodyFactory.DecideBody(method, implMethod, _aspectValidator, _serviceType); + + var attributes = MethodBuilderConstants.OverrideMethodAttributes; + if (method.Attributes.HasFlag(MethodAttributes.Public)) + attributes |= MethodAttributes.Public; + if (method.Attributes.HasFlag(MethodAttributes.Family)) + attributes |= MethodAttributes.Family; + if (method.Attributes.HasFlag(MethodAttributes.FamORAssem)) + attributes |= MethodAttributes.FamORAssem; + + var node = InterfaceImplBuilder.BuildProxyMethod( + serviceMethod: method, + implMethod: implMethod, + name: method.Name, + attributes: attributes, + body: body, + overridesMethod: overridesMethod, + methodConstants: methodConstants); + + return node; + } + private void BuildClassMethods(List methods, List methodConstants) { var covariantReturnMethods = _implType.GetCovariantReturnMethods(); @@ -134,43 +158,41 @@ private void BuildClassMethods(List methods, List m.OverridenMethod.IsSameBaseDefinition(method)); - var overriden = covariantReturn.OverridenMethod; - if (overriden == null) + var covariantReturn = covariantReturnMethods.FirstOrDefault(m => m.OverriddenMethod.IsSameBaseDefinition(method)); + var overridden = covariantReturn.OverriddenMethod; + if (overridden == null) return (null, false); - // if method is the base definition of the overriden method, the CovariantReturnMethod is not in serviceType, so we need to add CovariantReturnMethod to implType. - // otherwise, the CovariantReturnMethod is also in serviceType, which will be added to implType in next for-loops. - return overriden.GetBaseDefinition() == method - ? (covariantReturn.CovariantReturnMethod, true) + // If the current method is the base definition of a covariant-return override chain, + // the actual covariant-return method will not appear in _serviceType's method list. + // In that case, use CovariantReturnMethod instead. + // + // Otherwise, the covariant-return method will be visited in a later iteration, + // so skip the current method to avoid duplicate processing. + return overridden.GetBaseDefinition() == method + ? (covariantReturn.CovariantReturnMethod, false) : (null, true); } } private void BuildClassProperties(List properties, List methods, List methodConstants) { + var covariantReturnMethods = _implType.GetCovariantReturnMethods(); + foreach (var property in _serviceType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { if (!property.IsVisibleAndVirtual()) @@ -181,38 +203,29 @@ private void BuildClassProperties(List properties, List @@ -223,6 +236,42 @@ private void BuildClassProperties(List properties, List IsOverriddenByCovariantReturnProperty(property, m)); + var overridden = covariantReturn.OverriddenMethod; + if (overridden == null) + return (null, false); + + // If the property's getter is the base definition of a covariant-return override chain, + // the actual covariant-return getter will not appear in _serviceType's property list. + // In that case, use CovariantReturnMethod instead. + // + // Otherwise, the covariant-return property will be visited in a later iteration, + // so skip the current property to avoid duplicate processing. + return overridden.GetBaseDefinition() == property.GetMethod + ? (covariantReturn.CovariantReturnMethod, false) + : (null, true); + } + + bool IsOverriddenByCovariantReturnProperty(PropertyInfo property, CovariantReturnMethodInfo info) + { + // this case occurs when the property is not overridden in the serviceType. + var get = property.GetMethod; + if (info.OverriddenMethod.IsSameBaseDefinition(get)) + return true; + + // this case occurs when the property is overridden in the serviceType. + // in this case, the property type is super class of (and not the same as) the getter's type. + var covariantReturn = info.CovariantReturnMethod; + if (covariantReturn.IsSameBaseDefinition(get) + && covariantReturn.ReturnType != property.PropertyType + && property.PropertyType.IsAssignableFrom(covariantReturn.ReturnType)) + return true; + + return false; + } } private void BuildAdditionalInterfaceMembers(List methods, List properties, List methodConstants) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs index 8767cf4a..a92c6931 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs @@ -272,7 +272,7 @@ internal static void BuildInterfaceProxyMembers( { var covariantReturnGetter = FindCovariantReturnGetter(property); properties.Add(BuildProxyProperty(property, property.Name, resolvedImplType, aspectValidator, - interfaceType, MethodBuilderConstants.InterfaceMethodAttributes, methods, methodConstants)); + interfaceType, MethodBuilderConstants.InterfaceMethodAttributes, methods, methodConstants, covariantReturnGetter)); } // Additional interface properties (explicit) @@ -280,8 +280,9 @@ internal static void BuildInterfaceProxyMembers( { foreach (var property in iface.GetTypeInfo().DeclaredProperties) { + var covariantReturnGetter = FindCovariantReturnGetter(property); properties.Add(BuildProxyProperty(property, property.GetDisplayName(), resolvedImplType, aspectValidator, - interfaceType, MethodBuilderConstants.ExplicitMethodAttributes, methods, methodConstants)); + interfaceType, MethodBuilderConstants.ExplicitMethodAttributes, methods, methodConstants, covariantReturnGetter)); } } @@ -308,7 +309,8 @@ private static PropertyNode BuildProxyProperty( Type serviceType, MethodAttributes methodAttrs, List methods, - List methodConstants) + List methodConstants, + MethodInfo covariantReturnGetter) { MethodNode getMethod = null; MethodNode setMethod = null; @@ -316,7 +318,7 @@ private static PropertyNode BuildProxyProperty( if (property.CanRead) { var method = property.GetMethod; - var implMethod = ResolveImplementationMethod(method, implType); + var implMethod = covariantReturnGetter ?? ResolveImplementationMethod(method, implType); var body = MethodBodyFactory.DecideBody(method, implMethod, aspectValidator, serviceType); var overrides = methodAttrs == MethodBuilderConstants.ExplicitMethodAttributes ? method : method; getMethod = BuildProxyMethod(method, implMethod, methodAttrs == MethodBuilderConstants.ExplicitMethodAttributes ? method.GetName() : method.Name, diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs index dbb62c4e..7d511297 100644 --- a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -25,17 +25,57 @@ public static IEnumerable GetInterfaceDeclarations(this MethodInfo m } } - public static bool IsPreserveBaseOverride(this MethodInfo method, bool checkBase) + /// + /// Determines whether the method itself is a covariant-return override method. + /// + /// + /// The method to inspect. + /// + /// + /// if the method is marked with + /// PreserveBaseOverridesAttribute; otherwise, + /// . + /// + public static bool IsCovariantReturnMethod(this MethodInfo method) { - if (PreserveBaseOverridesAttribute is null) - return false; + return PreserveBaseOverridesAttribute != null + && method.IsDefined(PreserveBaseOverridesAttribute); + } - if (method.IsDefined(PreserveBaseOverridesAttribute)) + /// + /// Determines whether the method participates in a covariant-return override chain. + /// + /// + /// The method to inspect. + /// + /// + /// if the method itself, or its base definition, + /// is a covariant-return override method; otherwise, + /// . + /// + public static bool IsInCovariantReturnChain(this MethodInfo method) + { + if (method.IsCovariantReturnMethod()) return true; - return checkBase && method.GetBaseDefinition().IsDefined(PreserveBaseOverridesAttribute); + return method.GetBaseDefinition() + .IsCovariantReturnMethod(); } + /// + /// Determines whether two methods belong to the same virtual override chain + /// by comparing their base definitions. + /// + /// + /// The first method to compare. + /// + /// + /// The second method to compare. + /// + /// + /// if both methods have the same base definition; + /// otherwise, . + /// public static bool IsSameBaseDefinition(this MethodInfo method, MethodInfo other) { return method.GetBaseDefinition() == other.GetBaseDefinition(); diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 1e4d0bb4..c94a2d67 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -26,7 +26,7 @@ internal readonly struct CovariantReturnMethodInfo /// base definition when covariant return types are involved. /// /// - public readonly MethodInfo OverridenMethod; + public readonly MethodInfo OverriddenMethod; /// /// The set of interface method declarations (if any) @@ -34,10 +34,10 @@ internal readonly struct CovariantReturnMethodInfo /// public readonly HashSet InterfaceDeclarations; - public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo overridenMethod, HashSet interfaceDeclarations) + public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo overriddenMethod, HashSet interfaceDeclarations) { InterfaceDeclarations = interfaceDeclarations; - OverridenMethod = overridenMethod; + OverriddenMethod = overriddenMethod; CovariantReturnMethod = covariantReturnMethod; } } @@ -55,7 +55,7 @@ public static IReadOnlyList GetCovariantReturnMethods var methods = type .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .GroupBy(m => m.IsPreserveBaseOverride(true)) + .GroupBy(m => m.IsInCovariantReturnChain()) .ToDictionary(m => m.Key, m => m.ToArray()); var covariantReturnMethods = methods.GetValueOrDefault(true, Array.Empty()); @@ -63,12 +63,12 @@ public static IReadOnlyList GetCovariantReturnMethods foreach (var covariantReturnMethod in covariantReturnMethods) { - var overridenMethod = otherMethods.FirstOrDefault(m => Match(covariantReturnMethod, m)); - if (overridenMethod is null) + var overriddenMethod = otherMethods.FirstOrDefault(m => Match(covariantReturnMethod, m)); + if (overriddenMethod is null) continue; var interfaceDeclarations = covariantReturnMethod.GetInterfaceDeclarations().ToHashSet(); - result.Add(new CovariantReturnMethodInfo(covariantReturnMethod, overridenMethod, interfaceDeclarations)); + result.Add(new CovariantReturnMethodInfo(covariantReturnMethod, overriddenMethod, interfaceDeclarations)); } return result; diff --git a/src/AspectCore.Core/Properties/AssemblyInfo.cs b/src/AspectCore.Core/Properties/AssemblyInfo.cs index b2f77286..c92ad180 100644 --- a/src/AspectCore.Core/Properties/AssemblyInfo.cs +++ b/src/AspectCore.Core/Properties/AssemblyInfo.cs @@ -29,4 +29,13 @@ "59678c9b")] #endif +#if DEBUG [assembly: InternalsVisibleTo("AspectCore.Tests")] +#else +[assembly: InternalsVisibleTo("AspectCore.Tests, PublicKey=" + + "0024000004800000940000000602000000240000525341310004000001000100e5a34dfa0bd597" + + "39067521c28b809e6653358a008148f35c8d3357dc02d90ef3eb3365fb55903bdcd14dbfe2b73a" + + "10361c71c948b5ffcec2bf17e6c7a2ef98494d34d6e00d671b32566d153b8139d1caa0d5a9b071" + + "e15b6849fbabea83fc9b8b6abf959e606f5e51b268a6a6c2d4757bbc3ae33689373faaedf61077" + + "59678c9b")] +#endif diff --git a/src/AspectCore.Extensions.Reflection/Extensions/MethodExtensions.cs b/src/AspectCore.Extensions.Reflection/Extensions/MethodExtensions.cs index c66b6c9f..2749373e 100644 --- a/src/AspectCore.Extensions.Reflection/Extensions/MethodExtensions.cs +++ b/src/AspectCore.Extensions.Reflection/Extensions/MethodExtensions.cs @@ -25,21 +25,23 @@ public static PropertyInfo GetBindingProperty(this MethodInfo method) } return dictionary.GetOrAdd(method, m => - { - foreach (var property in m.DeclaringType.GetTypeInfo().GetProperties()) - { - if (property.CanRead && property.GetMethod == m) - { - return property; - } + { + // the method may be a reflected method, so get the base definition and then check equality. + var baseDef = method.GetBaseDefinition(); + foreach (var property in m.DeclaringType.GetTypeInfo().GetProperties()) + { + if (property.CanRead && property.GetMethod == baseDef) + { + return property; + } - if (property.CanWrite && property.SetMethod == m) - { - return property; - } - } - return null; - }); + if (property.CanWrite && property.SetMethod == baseDef) + { + return property; + } + } + return null; + }); } } } \ No newline at end of file diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index 8ba5ca11..cae72fab 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -65,10 +65,10 @@ public class DerivedCovariantReturnsService : CovariantReturnsService public void CreateClassProxy_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); } [Fact] @@ -85,49 +85,49 @@ public void CreateClassProxy_DerivedCovariantReturnsService_Test() public void CreateClassProxy_Service_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); } [Fact] public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); } [Fact] public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); } [Fact] public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() { var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); } [Fact] public void CreateInterfaceProxy_IService_DerivedCovariantReturnsService_Test() { var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); } } diff --git a/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs b/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs index 082dc376..61381ca7 100644 --- a/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs +++ b/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs @@ -7,7 +7,7 @@ namespace AspectCore.Tests.Extensions; public class CovariantReturnTests { - // 1️⃣ Basic covariant override between classes + // Basic covariant override between classes private class Animal { } private class Dog : Animal { } @@ -31,9 +31,9 @@ public void FindsBasicCovariantReturnMethod() var info = methods.Single(); Assert.Equal(nameof(DerivedClass.Make), info.CovariantReturnMethod.Name); Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); - Assert.Equal(typeof(Animal), info.OverridenMethod.ReturnType); - Assert.True(info.OverridenMethod.DeclaringType == typeof(DerivedClass) - || info.OverridenMethod.DeclaringType == typeof(BaseClass)); + Assert.Equal(typeof(Animal), info.OverriddenMethod.ReturnType); + Assert.True(info.OverriddenMethod.DeclaringType == typeof(DerivedClass) + || info.OverriddenMethod.DeclaringType == typeof(BaseClass)); } // Multi-level inheritance @@ -76,7 +76,7 @@ public void HandlesInterfaceCovariantReturnCorrectly() { var info = typeof(FancyWidgetFactory).GetCovariantReturnMethods().Single(); Assert.Equal(typeof(FancyWidget), info.CovariantReturnMethod.ReturnType); - Assert.Equal(typeof(Widget), info.OverridenMethod.ReturnType); + Assert.Equal(typeof(Widget), info.OverriddenMethod.ReturnType); // Should list IFactory.Create() as an interface declaration Assert.Contains(info.InterfaceDeclarations, m => m.DeclaringType!.GetGenericTypeDefinition() == typeof(IFactory<>)); } @@ -116,8 +116,8 @@ public void WorksForGenericCovariantReturn() var info = typeof(GenDerived).GetCovariantReturnMethods().Single(); Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); Assert.Empty(info.CovariantReturnMethod.GetGenericArguments()); - Assert.Empty(info.OverridenMethod.GetGenericArguments()); - Assert.NotEqual(info.OverridenMethod, info.OverridenMethod.GetBaseDefinition()); + Assert.Empty(info.OverriddenMethod.GetGenericArguments()); + Assert.NotEqual(info.OverriddenMethod, info.OverriddenMethod.GetBaseDefinition()); } // Same slot verification From c8f303517d2893ac740801bd3344b58dcc5d4484 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 13 May 2026 19:43:48 -0230 Subject: [PATCH 23/55] sign the project AspectCore.Tests.csproj to test internal methods --- .../Extensions/TypeExtensions.cs | 29 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 10 +++---- .../ConfigurationBindingTest.cs | 2 +- .../ConfigurationValueTest.cs | 2 +- .../AspectCore.Tests/AspectCore.Tests.csproj | 1 + 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index c94a2d67..0d9538be 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -46,6 +46,35 @@ internal static class TypeExtensions { public static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); + /// + /// Finds methods participating in covariant-return overrides on the specified type + /// and matches them with their non-covariant overridden methods. + /// + /// + /// The type whose methods should be inspected. + /// + /// + /// A collection of containing: + /// + /// + /// + /// The covariant-return method. + /// + /// + /// + /// + /// The corresponding overridden method with the original return type. + /// + /// + /// + /// + /// The interface methods implemented by the covariant-return method. + /// + /// + /// + /// Returns an empty collection if the current runtime does not support + /// covariant return types. + /// public static IReadOnlyList GetCovariantReturnMethods(this Type type) { var result = new List(); diff --git a/src/AspectCore.Core/Properties/AssemblyInfo.cs b/src/AspectCore.Core/Properties/AssemblyInfo.cs index c92ad180..49a44664 100644 --- a/src/AspectCore.Core/Properties/AssemblyInfo.cs +++ b/src/AspectCore.Core/Properties/AssemblyInfo.cs @@ -33,9 +33,9 @@ [assembly: InternalsVisibleTo("AspectCore.Tests")] #else [assembly: InternalsVisibleTo("AspectCore.Tests, PublicKey=" + - "0024000004800000940000000602000000240000525341310004000001000100e5a34dfa0bd597" + - "39067521c28b809e6653358a008148f35c8d3357dc02d90ef3eb3365fb55903bdcd14dbfe2b73a" + - "10361c71c948b5ffcec2bf17e6c7a2ef98494d34d6e00d671b32566d153b8139d1caa0d5a9b071" + - "e15b6849fbabea83fc9b8b6abf959e606f5e51b268a6a6c2d4757bbc3ae33689373faaedf61077" + - "59678c9b")] + "0024000004800000940000000602000000240000525341310004000001000100E5A34DFA0BD597" + + "39067521C28B809E6653358A008148F35C8D3357DC02D90EF3EB3365FB55903BDCD14DBFE2B73A" + + "10361C71C948B5FFCEC2BF17E6C7A2EF98494D34D6E00D671B32566D153B8139D1CAA0D5A9B071" + + "E15B6849FBABEA83FC9B8B6ABF959E606F5E51B268A6A6C2D4757BBC3AE33689373FAAEDF61077" + + "59678C9B")] #endif diff --git a/tests/AspectCore.Extensions.Configuration.Tests/ConfigurationBindingTest.cs b/tests/AspectCore.Extensions.Configuration.Tests/ConfigurationBindingTest.cs index efb3675b..0bebd9fb 100644 --- a/tests/AspectCore.Extensions.Configuration.Tests/ConfigurationBindingTest.cs +++ b/tests/AspectCore.Extensions.Configuration.Tests/ConfigurationBindingTest.cs @@ -23,7 +23,7 @@ public void LoadBinding() container.AddConfigurationInject(); container.AddType(); var service = container.Build().Resolve(); - Assert.Equal(service.ToString(), "lemon-24"); + Assert.Equal("lemon-24", service.ToString()); } } diff --git a/tests/AspectCore.Extensions.Configuration.Tests/ConfigurationValueTest.cs b/tests/AspectCore.Extensions.Configuration.Tests/ConfigurationValueTest.cs index f6c331f6..8d61298c 100644 --- a/tests/AspectCore.Extensions.Configuration.Tests/ConfigurationValueTest.cs +++ b/tests/AspectCore.Extensions.Configuration.Tests/ConfigurationValueTest.cs @@ -23,7 +23,7 @@ public void LoadValue() container.AddConfigurationInject(); container.AddType(); var service = container.Build().Resolve(); - Assert.Equal(service.ToString(), "lemon-24"); + Assert.Equal("lemon-24", service.ToString()); } } diff --git a/tests/AspectCore.Tests/AspectCore.Tests.csproj b/tests/AspectCore.Tests/AspectCore.Tests.csproj index 0debc9ad..9bd440e4 100644 --- a/tests/AspectCore.Tests/AspectCore.Tests.csproj +++ b/tests/AspectCore.Tests/AspectCore.Tests.csproj @@ -1,4 +1,5 @@  + net9.0;net8.0;net7.0;net6.0 false From 1af526a77aaeaa51b3919cdbfa90e52b3fe13145 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 5 Jul 2026 22:27:24 -0230 Subject: [PATCH 24/55] add test cases for multi level --- .../MultiLevelCovariantReturnMethodTests.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs diff --git a/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs new file mode 100644 index 00000000..8afd2fe8 --- /dev/null +++ b/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs @@ -0,0 +1,82 @@ +using System.Linq; +using System.Threading.Tasks; +using AspectCore.DynamicProxy; +using AspectCore.Extensions; +using Xunit; + +namespace AspectCore.Tests.DynamicProxy; + +public class MultiLevelCovariantReturnMethodTests : DynamicProxyTestBase +{ + public class ReturnTypeInterceptor : AbstractInterceptorAttribute + { + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + await context.Invoke(next); + + context.ReturnValue = new LeafResult(context.ImplementationMethod.ReturnType.Name); + } + } + + public class BaseResult + { + public BaseResult(string implementationReturnType) + { + ImplementationReturnType = implementationReturnType; + } + + public string ImplementationReturnType { get; } + } + + public class MidResult : BaseResult + { + public MidResult(string implementationReturnType) : base(implementationReturnType) + { + } + } + + public class LeafResult : MidResult + { + public LeafResult(string implementationReturnType) : base(implementationReturnType) + { + } + } + + public class BaseService + { + [ReturnTypeInterceptor] + public virtual BaseResult Create() => new(nameof(BaseResult)); + } + + public class MidService : BaseService + { + public override MidResult Create() => new(nameof(MidResult)); + } + + public class LeafService : MidService + { + public override LeafResult Create() => new(nameof(LeafResult)); + } + + [Fact] + public void CreateClassProxy_MultiLevelCovariantReturn_UsesLeafOverride_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + + var result = service.Create(); + + Assert.Equal(nameof(LeafResult), result.ImplementationReturnType); + } + + [Fact] + public void GetCovariantReturnMethods_MultiLevelCovariantReturn_ReportsOnlyLeafOverride_Test() + { + var baseMethod = typeof(BaseService).GetMethod(nameof(BaseService.Create))!; + var methods = typeof(LeafService).GetCovariantReturnMethods() + .Where(x => x.OverriddenMethod.GetBaseDefinition() == baseMethod) + .ToArray(); + + var method = Assert.Single(methods); + Assert.Equal(typeof(LeafResult), method.CovariantReturnMethod.ReturnType); + } +} From 6a8c6e20f34fedc646774e82572f3bd40eba9650 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Sun, 5 Jul 2026 22:37:35 -0230 Subject: [PATCH 25/55] add test cases --- .editorconfig | 4 + AspectCore-Framework.sln | 5 +- .../AspectCore.Tests/AspectCore.Tests.csproj | 1 + .../CovariantReturnMethodTests.cs | 126 +++++++++--------- .../MultiLevelCovariantReturnMethodTests.cs | 26 +--- 5 files changed, 75 insertions(+), 87 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..266ea748 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# IDE0290: Use primary constructor +dotnet_diagnostic.IDE0290.severity = silent \ No newline at end of file diff --git a/AspectCore-Framework.sln b/AspectCore-Framework.sln index 07362618..2b39daf6 100644 --- a/AspectCore-Framework.sln +++ b/AspectCore-Framework.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.12.35514.174 d17.12 +# Visual Studio Version 18 +VisualStudioVersion = 18.7.11925.98 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{73F38B6C-1A05-41C8-8029-D1F2F41D3279}" EndProject @@ -17,6 +17,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspectCore.Extensions.Refle EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "items", "items", "{D9666E6E-3283-40FF-9965-12A6752DA18B}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore build.cake = build.cake diff --git a/tests/AspectCore.Tests/AspectCore.Tests.csproj b/tests/AspectCore.Tests/AspectCore.Tests.csproj index 9bd440e4..79de9924 100644 --- a/tests/AspectCore.Tests/AspectCore.Tests.csproj +++ b/tests/AspectCore.Tests/AspectCore.Tests.csproj @@ -3,6 +3,7 @@ net9.0;net8.0;net7.0;net6.0 false + 13.0 diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index cae72fab..19a3fa02 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -6,7 +6,7 @@ namespace AspectCore.Tests.DynamicProxy; public class CovariantReturnMethodTests : DynamicProxyTestBase { - public class Interceptor : AbstractInterceptorAttribute + public class ReturnTypeInterceptor : AbstractInterceptorAttribute { public override async Task Invoke(AspectContext context, AspectDelegate next) { @@ -15,119 +15,119 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) var returnType = context.ImplementationMethod.ReturnType; if (returnType == typeof(string)) { - context.ReturnValue += nameof(Interceptor); + context.ReturnValue += nameof(ReturnTypeInterceptor); } else if (returnType == typeof(object)) { - context.ReturnValue = nameof(Interceptor); + context.ReturnValue = nameof(ReturnTypeInterceptor); } } } - public interface IService + public interface ICovariantReturnService { object Property { get; } object Method(); - object ProxyProperty { [Interceptor] get; } - [Interceptor] - object ProxyMethod(); + object InterceptedProperty { [ReturnTypeInterceptor] get; } + [ReturnTypeInterceptor] + object InterceptedMethod(); } - public class Service : IService + public class BaseCovariantReturnService : ICovariantReturnService { public virtual object Property { get; } = 1; public virtual object Method() => 1; - public virtual object ProxyProperty { [Interceptor] get; } = new(); - [Interceptor] - public virtual object ProxyMethod() => new(); + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = new(); + [ReturnTypeInterceptor] + public virtual object InterceptedMethod() => new(); } - public class CovariantReturnsService : Service + public class StringCovariantReturnService : BaseCovariantReturnService { - public override string Property { get; } = nameof(CovariantReturnsService); - public override string Method() => nameof(CovariantReturnsService); + public override string Property { get; } = nameof(StringCovariantReturnService); + public override string Method() => nameof(StringCovariantReturnService); - public override string ProxyProperty { [Interceptor] get; } = nameof(CovariantReturnsService); - [Interceptor] - public override string ProxyMethod() => nameof(CovariantReturnsService); + public override string InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(StringCovariantReturnService); + [ReturnTypeInterceptor] + public override string InterceptedMethod() => nameof(StringCovariantReturnService); } - public class DerivedCovariantReturnsService : CovariantReturnsService + public class DerivedStringCovariantReturnService : StringCovariantReturnService { - public override string ProxyProperty { [Interceptor] get; } = nameof(DerivedCovariantReturnsService); - [Interceptor] - public override string ProxyMethod() => nameof(DerivedCovariantReturnsService); + public override string InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(DerivedStringCovariantReturnService); + [ReturnTypeInterceptor] + public override string InterceptedMethod() => nameof(DerivedStringCovariantReturnService); } [Fact] - public void CreateClassProxy_CovariantReturnsService_Test() + public void CreateClassProxy_ForCovariantReturnType_ShouldUseStringReturnType() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); - Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(StringCovariantReturnService), service.Method()); + Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); + Assert.Equal(nameof(StringCovariantReturnService), service.Property); + Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); } [Fact] - public void CreateClassProxy_DerivedCovariantReturnsService_Test() + public void CreateClassProxy_ForDerivedCovariantReturnType_ShouldUseOverriddenInterceptedMembers() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(StringCovariantReturnService), service.Property); + Assert.Equal(nameof(StringCovariantReturnService), service.Method()); + Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); + Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); } [Fact] - public void CreateClassProxy_Service_CovariantReturnsService_Test() + public void CreateClassProxy_ForBaseServiceAndCovariantImplementation_ShouldUseStringReturnType() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); - Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(StringCovariantReturnService), service.Method()); + Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); + Assert.Equal(nameof(StringCovariantReturnService), service.Property); + Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); } [Fact] - public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() + public void CreateClassProxy_ForBaseServiceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); - Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(StringCovariantReturnService), service.Method()); + Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); + Assert.Equal(nameof(StringCovariantReturnService), service.Property); + Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); } [Fact] - public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsService_Test() + public void CreateClassProxy_ForCovariantServiceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); - Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(StringCovariantReturnService), service.Method()); + Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); + Assert.Equal(nameof(StringCovariantReturnService), service.Property); + Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); } [Fact] - public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() + public void CreateInterfaceProxy_ForBaseInterfaceAndCovariantImplementation_ShouldUseStringReturnType() { - var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); - Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + var service = ProxyGenerator.CreateInterfaceProxy(); + Assert.Equal(nameof(StringCovariantReturnService), service.Method()); + Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); + Assert.Equal(nameof(StringCovariantReturnService), service.Property); + Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); } [Fact] - public void CreateInterfaceProxy_IService_DerivedCovariantReturnsService_Test() + public void CreateInterfaceProxy_ForBaseInterfaceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { - var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(CovariantReturnsService), service.Method()); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); - Assert.Equal(nameof(CovariantReturnsService), service.Property); - Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + var service = ProxyGenerator.CreateInterfaceProxy(); + Assert.Equal(nameof(StringCovariantReturnService), service.Method()); + Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); + Assert.Equal(nameof(StringCovariantReturnService), service.Property); + Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); } } diff --git a/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs index 8afd2fe8..81e04cc3 100644 --- a/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs @@ -13,34 +13,18 @@ public class ReturnTypeInterceptor : AbstractInterceptorAttribute public override async Task Invoke(AspectContext context, AspectDelegate next) { await context.Invoke(next); - context.ReturnValue = new LeafResult(context.ImplementationMethod.ReturnType.Name); } } - public class BaseResult + public class BaseResult(string implementationReturnType) { - public BaseResult(string implementationReturnType) - { - ImplementationReturnType = implementationReturnType; - } - - public string ImplementationReturnType { get; } + public string ImplementationReturnType { get; } = implementationReturnType; } - public class MidResult : BaseResult - { - public MidResult(string implementationReturnType) : base(implementationReturnType) - { - } - } + public class MidResult(string implementationReturnType) : BaseResult(implementationReturnType); - public class LeafResult : MidResult - { - public LeafResult(string implementationReturnType) : base(implementationReturnType) - { - } - } + public class LeafResult(string implementationReturnType) : MidResult(implementationReturnType); public class BaseService { @@ -62,9 +46,7 @@ public class LeafService : MidService public void CreateClassProxy_MultiLevelCovariantReturn_UsesLeafOverride_Test() { var service = ProxyGenerator.CreateClassProxy(); - var result = service.Create(); - Assert.Equal(nameof(LeafResult), result.ImplementationReturnType); } From d623d5a406f2f1b92851cf101d5dc44b73a8374c Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Mon, 6 Jul 2026 01:58:34 -0230 Subject: [PATCH 26/55] refine code --- .editorconfig | 4 - AspectCore-Framework.sln | 1 - build/common.props | 2 + .../Builders/ClassProxyAstBuilder.cs | 14 +- .../Builders/InterfaceImplAstBuilder.cs | 15 +- .../Extensions/TypeExtensions.cs | 43 ++++- .../CovariantReturnMethodTests.cs | 155 +++++++++++------- .../MultiLevelCovariantReturnMethodTests.cs | 64 -------- 8 files changed, 161 insertions(+), 137 deletions(-) delete mode 100644 .editorconfig delete mode 100644 tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 266ea748..00000000 --- a/.editorconfig +++ /dev/null @@ -1,4 +0,0 @@ -[*.cs] - -# IDE0290: Use primary constructor -dotnet_diagnostic.IDE0290.severity = silent \ No newline at end of file diff --git a/AspectCore-Framework.sln b/AspectCore-Framework.sln index 2b39daf6..59ea1f01 100644 --- a/AspectCore-Framework.sln +++ b/AspectCore-Framework.sln @@ -17,7 +17,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspectCore.Extensions.Refle EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "items", "items", "{D9666E6E-3283-40FF-9965-12A6752DA18B}" ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore build.cake = build.cake diff --git a/build/common.props b/build/common.props index c9b45319..54c727c6 100644 --- a/build/common.props +++ b/build/common.props @@ -14,5 +14,7 @@ false False False + + 10.0 \ No newline at end of file diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index e6108d1c..b0ccad6c 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs @@ -172,7 +172,11 @@ private void BuildClassMethods(List methods, List m.OverriddenMethod.IsSameBaseDefinition(method)); + var covariantReturn = covariantReturnMethods + .Where(m => m.OverriddenMethod.IsSameBaseDefinition(method)) + .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method + .FirstOrDefault(); + var overridden = covariantReturn.OverriddenMethod; if (overridden == null) return (null, false); @@ -183,6 +187,8 @@ private void BuildClassMethods(List methods, List properties, List IsOverriddenByCovariantReturnProperty(property, m)); + var covariantReturn = covariantReturnMethods + .Where(m => IsOverriddenByCovariantReturnProperty(property, m)) + .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method + .FirstOrDefault(); + var overridden = covariantReturn.OverriddenMethod; if (overridden == null) return (null, false); diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs index a92c6931..ca3f82fa 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs @@ -289,15 +289,22 @@ internal static void BuildInterfaceProxyMembers( MethodInfo FindCovariantReturnMethod(MethodInfo interfaceMethod) { return covariantReturnMethods - .FirstOrDefault(m => m.InterfaceDeclarations.Contains(interfaceMethod)) + .Where(m => m.InterfaceDeclarations.Contains(interfaceMethod)) + .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method + .FirstOrDefault() .CovariantReturnMethod; } MethodInfo FindCovariantReturnGetter(PropertyInfo property) { - return property.CanRead - ? covariantReturnMethods.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod - : null; + if (!property.CanRead) + return null; + + return covariantReturnMethods + .Where(m => m.InterfaceDeclarations.Contains(property.GetMethod)) + .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method + .FirstOrDefault() + .CovariantReturnMethod; } } diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 0d9538be..2059841c 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -1,9 +1,9 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -// ReSharper disable once CheckNamespace namespace AspectCore.Extensions { internal readonly struct CovariantReturnMethodInfo @@ -14,6 +14,11 @@ internal readonly struct CovariantReturnMethodInfo /// public readonly MethodInfo CovariantReturnMethod; + /// + /// Gets the inheritance depth of the type that declares the . + /// + public readonly int InheritanceDepth; + /// /// Gets the method that is overridden or implemented by . /// @@ -39,12 +44,13 @@ public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo ov InterfaceDeclarations = interfaceDeclarations; OverriddenMethod = overriddenMethod; CovariantReturnMethod = covariantReturnMethod; + InheritanceDepth = covariantReturnMethod.DeclaringType.GetInheritanceDepth(); } } internal static class TypeExtensions { - public static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); + public static readonly Type? PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); /// /// Finds methods participating in covariant-return overrides on the specified type @@ -102,7 +108,7 @@ public static IReadOnlyList GetCovariantReturnMethods return result; - bool Match(MethodInfo covariantReturnMethod, MethodInfo other) + static bool Match(MethodInfo covariantReturnMethod, MethodInfo other) { if (covariantReturnMethod.Name != other.Name) return false; @@ -147,5 +153,34 @@ bool Match(MethodInfo covariantReturnMethod, MethodInfo other) return true; } } + + /// + /// Gets the inheritance depth of the specified type. + /// + /// The type whose inheritance depth is calculated. + /// + /// The inheritance depth of . + /// Returns 0 for , 1 for a class that directly inherits from , + /// 2 for its derived class, and so on. + /// + /// + /// Thrown when is . + /// + public static int GetInheritanceDepth(this Type? type) + { + if (type is null) + return 0; + + var depth = 0; + var current = type; + + while (current.BaseType != null) + { + depth++; + current = current.BaseType; + } + + return depth - 1; // 去掉 object 自己那一层 + } } } diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index 19a3fa02..1359396e 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -1,10 +1,11 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using AspectCore.DynamicProxy; using Xunit; namespace AspectCore.Tests.DynamicProxy; -public class CovariantReturnMethodTests : DynamicProxyTestBase +public class CovariantReturnMethodTests2 : DynamicProxyTestBase { public class ReturnTypeInterceptor : AbstractInterceptorAttribute { @@ -12,19 +13,23 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) { await context.Invoke(next); - var returnType = context.ImplementationMethod.ReturnType; - if (returnType == typeof(string)) + if (context.ReturnValue is BaseResult returnValue) { - context.ReturnValue += nameof(ReturnTypeInterceptor); - } - else if (returnType == typeof(object)) - { - context.ReturnValue = nameof(ReturnTypeInterceptor); + returnValue.Name += nameof(ReturnTypeInterceptor); } } } - public interface ICovariantReturnService + public class BaseResult(string name) + { + public string Name { get; set; } = name; + } + + public class MidResult(string name) : BaseResult(name); + + public class LeafResult(string name) : MidResult(name); + + public interface IService { object Property { get; } object Method(); @@ -34,100 +39,134 @@ public interface ICovariantReturnService object InterceptedMethod(); } - public class BaseCovariantReturnService : ICovariantReturnService + public class Service : IService { - public virtual object Property { get; } = 1; - public virtual object Method() => 1; + public virtual object Property { get; } = nameof(Property); + public virtual object Method() => nameof(Method); - public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = new(); + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); [ReturnTypeInterceptor] - public virtual object InterceptedMethod() => new(); + public virtual object InterceptedMethod() => nameof(InterceptedMethod); } - public class StringCovariantReturnService : BaseCovariantReturnService + public class BaseCovariantReturnService : Service { - public override string Property { get; } = nameof(StringCovariantReturnService); - public override string Method() => nameof(StringCovariantReturnService); + public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); + public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); - public override string InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(StringCovariantReturnService); + public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); [ReturnTypeInterceptor] - public override string InterceptedMethod() => nameof(StringCovariantReturnService); + public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); } - public class DerivedStringCovariantReturnService : StringCovariantReturnService + public class MidCovariantReturnService : BaseCovariantReturnService { - public override string InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(DerivedStringCovariantReturnService); + public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); + public override MidResult Method() => new(nameof(MidCovariantReturnService)); + + public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); [ReturnTypeInterceptor] - public override string InterceptedMethod() => nameof(DerivedStringCovariantReturnService); + public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); + } + + public class LeafCovariantReturnService : MidCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); + public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); + } + + /// + /// Verifies that an object is exactly the given type (and not a derived type), and that it satisfies the given predicate. + /// + private static void AssertTypeValue(object value, Func predicate) + { + var v = Assert.IsType(value); + Assert.True(predicate(v)); } [Fact] public void CreateClassProxy_ForCovariantReturnType_ShouldUseStringReturnType() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(StringCovariantReturnService), service.Method()); - Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); - Assert.Equal(nameof(StringCovariantReturnService), service.Property); - Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Method(), v => v.Name == nameof(BaseCovariantReturnService)); + AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Property, v => v.Name == nameof(BaseCovariantReturnService)); + AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); } [Fact] public void CreateClassProxy_ForDerivedCovariantReturnType_ShouldUseOverriddenInterceptedMembers() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(StringCovariantReturnService), service.Property); - Assert.Equal(nameof(StringCovariantReturnService), service.Method()); - Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); - Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); + AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); + AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); } [Fact] public void CreateClassProxy_ForBaseServiceAndCovariantImplementation_ShouldUseStringReturnType() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(StringCovariantReturnService), service.Method()); - Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); - Assert.Equal(nameof(StringCovariantReturnService), service.Property); - Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Property, v => v.Name == nameof(BaseCovariantReturnService)); + AssertTypeValue(service.Method(), v => v.Name == nameof(BaseCovariantReturnService)); + AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); } [Fact] public void CreateClassProxy_ForBaseServiceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(StringCovariantReturnService), service.Method()); - Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); - Assert.Equal(nameof(StringCovariantReturnService), service.Property); - Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); + AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); + AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); } [Fact] public void CreateClassProxy_ForCovariantServiceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { - var service = ProxyGenerator.CreateClassProxy(); - Assert.Equal(nameof(StringCovariantReturnService), service.Method()); - Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); - Assert.Equal(nameof(StringCovariantReturnService), service.Property); - Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); + AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); + AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); } [Fact] public void CreateInterfaceProxy_ForBaseInterfaceAndCovariantImplementation_ShouldUseStringReturnType() { - var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(StringCovariantReturnService), service.Method()); - Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); - Assert.Equal(nameof(StringCovariantReturnService), service.Property); - Assert.Equal(nameof(StringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); + var service = ProxyGenerator.CreateInterfaceProxy(); + AssertTypeValue(service.Property, v => v.Name == nameof(BaseCovariantReturnService)); + AssertTypeValue(service.Method(), v => v.Name == nameof(BaseCovariantReturnService)); + AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); } [Fact] public void CreateInterfaceProxy_ForBaseInterfaceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { - var service = ProxyGenerator.CreateInterfaceProxy(); - Assert.Equal(nameof(StringCovariantReturnService), service.Method()); - Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedMethod()); - Assert.Equal(nameof(StringCovariantReturnService), service.Property); - Assert.Equal(nameof(DerivedStringCovariantReturnService) + nameof(ReturnTypeInterceptor), service.InterceptedProperty); + var service = ProxyGenerator.CreateInterfaceProxy(); + AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); + AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); + AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); } + + //[Fact] + //public void GetCovariantReturnMethods_MultiLevelCovariantReturn_ReportsOnlyLeafOverride_Test() + //{ + // var baseMethod = typeof(BaseCovariantReturnService).GetMethod(nameof(BaseCovariantReturnService.InterceptedMethod))!; + // var methods = typeof(DerivedStringCovariantReturnService).GetCovariantReturnMethods() + // .Where(x => x.OverriddenMethod.GetBaseDefinition() == baseMethod) + // .ToArray(); + + // var method = Assert.Single(methods); + // Assert.Equal(typeof(string), method.CovariantReturnMethod.ReturnType); + //} } diff --git a/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs deleted file mode 100644 index 81e04cc3..00000000 --- a/tests/AspectCore.Tests/DynamicProxy/MultiLevelCovariantReturnMethodTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using AspectCore.DynamicProxy; -using AspectCore.Extensions; -using Xunit; - -namespace AspectCore.Tests.DynamicProxy; - -public class MultiLevelCovariantReturnMethodTests : DynamicProxyTestBase -{ - public class ReturnTypeInterceptor : AbstractInterceptorAttribute - { - public override async Task Invoke(AspectContext context, AspectDelegate next) - { - await context.Invoke(next); - context.ReturnValue = new LeafResult(context.ImplementationMethod.ReturnType.Name); - } - } - - public class BaseResult(string implementationReturnType) - { - public string ImplementationReturnType { get; } = implementationReturnType; - } - - public class MidResult(string implementationReturnType) : BaseResult(implementationReturnType); - - public class LeafResult(string implementationReturnType) : MidResult(implementationReturnType); - - public class BaseService - { - [ReturnTypeInterceptor] - public virtual BaseResult Create() => new(nameof(BaseResult)); - } - - public class MidService : BaseService - { - public override MidResult Create() => new(nameof(MidResult)); - } - - public class LeafService : MidService - { - public override LeafResult Create() => new(nameof(LeafResult)); - } - - [Fact] - public void CreateClassProxy_MultiLevelCovariantReturn_UsesLeafOverride_Test() - { - var service = ProxyGenerator.CreateClassProxy(); - var result = service.Create(); - Assert.Equal(nameof(LeafResult), result.ImplementationReturnType); - } - - [Fact] - public void GetCovariantReturnMethods_MultiLevelCovariantReturn_ReportsOnlyLeafOverride_Test() - { - var baseMethod = typeof(BaseService).GetMethod(nameof(BaseService.Create))!; - var methods = typeof(LeafService).GetCovariantReturnMethods() - .Where(x => x.OverriddenMethod.GetBaseDefinition() == baseMethod) - .ToArray(); - - var method = Assert.Single(methods); - Assert.Equal(typeof(LeafResult), method.CovariantReturnMethod.ReturnType); - } -} From 3dc0b0878eb2cce919282599cd67862168e2e252 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Mon, 6 Jul 2026 21:19:38 -0230 Subject: [PATCH 27/55] refine code --- .../Builders/ClassProxyAstBuilder.cs | 23 +++-- .../Extensions/TypeExtensions.cs | 92 +++++++++---------- .../CovariantReturnMethodTests.cs | 12 --- ...tReturnTests.cs => TypeExtensionsTests.cs} | 26 +++--- 4 files changed, 72 insertions(+), 81 deletions(-) rename tests/AspectCore.Tests/Extensions/{CovariantReturnTests.cs => TypeExtensionsTests.cs} (85%) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index b0ccad6c..7f02f1ec 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs @@ -181,17 +181,20 @@ private void BuildClassMethods(List methods, List GetCovariantReturnMethods foreach (var covariantReturnMethod in covariantReturnMethods) { - var overriddenMethod = otherMethods.FirstOrDefault(m => Match(covariantReturnMethod, m)); + var overriddenMethod = otherMethods.FirstOrDefault(m => m.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); if (overriddenMethod is null) continue; @@ -107,51 +107,6 @@ public static IReadOnlyList GetCovariantReturnMethods } return result; - - static bool Match(MethodInfo covariantReturnMethod, MethodInfo other) - { - if (covariantReturnMethod.Name != other.Name) - return false; - - // return types should not be the same. - if (covariantReturnMethod.ReturnType == other.ReturnType) - return false; - - if (other.ReturnType.IsAssignableFrom(covariantReturnMethod.ReturnType) == false) - return false; - - var params1 = covariantReturnMethod.GetParameters(); - var params2 = other.GetParameters(); - - if (params1.Length != params2.Length) - return false; - - foreach (var (p1, p2) in params1.Zip(params2)) - { - if (p1.ParameterType != p2.ParameterType) - return false; - } - - var isGeneric = covariantReturnMethod.IsGenericMethod; - if (isGeneric != other.IsGenericMethod) - return false; - - if (isGeneric) - { - var args1 = covariantReturnMethod.GetGenericArguments(); - var args2 = other.GetGenericArguments(); - if (args1.Length != args2.Length) - return false; - - foreach (var (a1, a2) in args1.Zip(args2)) - { - if (a1 != a2) - return false; - } - } - - return true; - } } /// @@ -182,5 +137,50 @@ public static int GetInheritanceDepth(this Type? type) return depth - 1; // 去掉 object 自己那一层 } + + public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, MethodInfo covariantReturnMethod) + { + if (covariantReturnMethod.Name != method.Name) + return false; + + // return types should not be the same. + if (covariantReturnMethod.ReturnType == method.ReturnType) + return false; + + if (method.ReturnType.IsAssignableFrom(covariantReturnMethod.ReturnType) == false) + return false; + + var params1 = covariantReturnMethod.GetParameters(); + var params2 = method.GetParameters(); + + if (params1.Length != params2.Length) + return false; + + foreach (var (p1, p2) in params1.Zip(params2)) + { + if (p1.ParameterType != p2.ParameterType) + return false; + } + + var isGeneric = covariantReturnMethod.IsGenericMethod; + if (isGeneric != method.IsGenericMethod) + return false; + + if (isGeneric) + { + var args1 = covariantReturnMethod.GetGenericArguments(); + var args2 = method.GetGenericArguments(); + if (args1.Length != args2.Length) + return false; + + foreach (var (a1, a2) in args1.Zip(args2)) + { + if (a1 != a2) + return false; + } + } + + return true; + } } } diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index 1359396e..e5df4e19 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -157,16 +157,4 @@ public void CreateInterfaceProxy_ForBaseInterfaceAndDerivedImplementation_Should AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); } - - //[Fact] - //public void GetCovariantReturnMethods_MultiLevelCovariantReturn_ReportsOnlyLeafOverride_Test() - //{ - // var baseMethod = typeof(BaseCovariantReturnService).GetMethod(nameof(BaseCovariantReturnService.InterceptedMethod))!; - // var methods = typeof(DerivedStringCovariantReturnService).GetCovariantReturnMethods() - // .Where(x => x.OverriddenMethod.GetBaseDefinition() == baseMethod) - // .ToArray(); - - // var method = Assert.Single(methods); - // Assert.Equal(typeof(string), method.CovariantReturnMethod.ReturnType); - //} } diff --git a/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs similarity index 85% rename from tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs rename to tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs index 61381ca7..f2a0aed8 100644 --- a/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs @@ -1,16 +1,16 @@ -using System.Linq; +using System.Linq; using System.Reflection; using AspectCore.Extensions; using Xunit; namespace AspectCore.Tests.Extensions; -public class CovariantReturnTests +public class TypeExtensionsTests { // Basic covariant override between classes - private class Animal { } + private class Animal; - private class Dog : Animal { } + private class Dog : Animal; private class BaseClass { @@ -23,7 +23,7 @@ private class DerivedClass : BaseClass } [Fact] - public void FindsBasicCovariantReturnMethod() + public void GetCovariantReturnMethods_FindsBasicCovariantReturnMethod() { var methods = typeof(DerivedClass).GetCovariantReturnMethods(); Assert.Single(methods); @@ -41,10 +41,10 @@ public void FindsBasicCovariantReturnMethod() private class B2 : B1 { public override B2 Clone() => new(); } - private class B3 : B2 { } + private class B3 : B2; [Fact] - public void DoesNotReportEmptyForDeeperHierarchy() + public void GetCovariantReturnMethods_DoesNotReportEmptyForDeeperHierarchy() { var methods = typeof(B3).GetCovariantReturnMethods(); // Should inherit from B2 → B1, so no new covariant method @@ -57,9 +57,9 @@ public void DoesNotReportEmptyForDeeperHierarchy() // Interface with covariant return private interface IFactory { T Create(); } - private class Widget { } + private class Widget; - private class FancyWidget : Widget { } + private class FancyWidget : Widget; private class WidgetFactory : IFactory { @@ -72,7 +72,7 @@ private class FancyWidgetFactory : WidgetFactory, IFactory } [Fact] - public void HandlesInterfaceCovariantReturnCorrectly() + public void GetCovariantReturnMethods_HandlesInterfaceCovariantReturnCorrectly() { var info = typeof(FancyWidgetFactory).GetCovariantReturnMethods().Single(); Assert.Equal(typeof(FancyWidget), info.CovariantReturnMethod.ReturnType); @@ -82,7 +82,7 @@ public void HandlesInterfaceCovariantReturnCorrectly() } // Explicit interface implementation - private interface ICreator { T Create(); } + private interface ICreator { T Create(); } private class Creator : ICreator { @@ -95,7 +95,7 @@ private class DogCreator : Creator, ICreator } [Fact] - public void FindsExplicitInterfaceImplementation() + public void GetCovariantReturnMethods_FindsExplicitInterfaceImplementation() { var infos = typeof(DogCreator).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) .Where(m => m.Name.Contains("ICreator")) @@ -111,7 +111,7 @@ private class GenBase { public virtual T Build() => default!; } private class GenDerived : GenBase { public override Dog Build() => new(); } [Fact] - public void WorksForGenericCovariantReturn() + public void GetCovariantReturnMethods_WorksForGenericCovariantReturn() { var info = typeof(GenDerived).GetCovariantReturnMethods().Single(); Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); From 52180dfb249df253af3e1fa0f726fe42553f4499 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Mon, 6 Jul 2026 21:33:28 -0230 Subject: [PATCH 28/55] clean up --- .../Extensions/TypeExtensions.cs | 1 + .../AspectCore.Tests/CovariantReturnTypes.cs | 79 ++++++++++++ .../CovariantReturnMethodTests.cs | 84 +----------- .../DynamicProxy/ProxyGeneratorTest.cs | 6 +- .../Extensions/TypeExtensionsTests.cs | 121 ------------------ 5 files changed, 89 insertions(+), 202 deletions(-) create mode 100644 tests/AspectCore.Tests/CovariantReturnTypes.cs diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index d1ef9ce8..2859aeb5 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; +// ReSharper disable once CheckNamespace namespace AspectCore.Extensions { internal readonly struct CovariantReturnMethodInfo diff --git a/tests/AspectCore.Tests/CovariantReturnTypes.cs b/tests/AspectCore.Tests/CovariantReturnTypes.cs new file mode 100644 index 00000000..8bd09d59 --- /dev/null +++ b/tests/AspectCore.Tests/CovariantReturnTypes.cs @@ -0,0 +1,79 @@ +using System.Threading.Tasks; +using AspectCore.DynamicProxy; + +namespace AspectCore.Tests; + +public class CovariantReturnTypes +{ + public class ReturnTypeInterceptor : AbstractInterceptorAttribute + { + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + await context.Invoke(next); + + if (context.ReturnValue is BaseResult returnValue) + { + returnValue.Name += nameof(ReturnTypeInterceptor); + } + } + } + + public class BaseResult(string name) + { + public string Name { get; set; } = name; + } + + public class MidResult(string name) : BaseResult(name); + + public class LeafResult(string name) : MidResult(name); + + public interface ICommonService + { + object Property { get; } + object Method(); + + object InterceptedProperty { [ReturnTypeInterceptor] get; } + [ReturnTypeInterceptor] + object InterceptedMethod(); + } + + public class CommonService : ICommonService + { + public virtual object Property { get; } = nameof(Property); + public virtual object Method() => nameof(Method); + + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); + [ReturnTypeInterceptor] + public virtual object InterceptedMethod() => nameof(InterceptedMethod); + } + + public class BaseCovariantReturnService : CommonService + { + public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); + public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); + + public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); + [ReturnTypeInterceptor] + public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); + } + + public class MidCovariantReturnService : BaseCovariantReturnService + { + public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); + public override MidResult Method() => new(nameof(MidCovariantReturnService)); + + public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); + [ReturnTypeInterceptor] + public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); + } + + public class LeafCovariantReturnService : MidCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); + public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); + } +} diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs index e5df4e19..c8fa97a5 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -1,84 +1,12 @@ using System; -using System.Threading.Tasks; using AspectCore.DynamicProxy; using Xunit; +using static AspectCore.Tests.CovariantReturnTypes; namespace AspectCore.Tests.DynamicProxy; -public class CovariantReturnMethodTests2 : DynamicProxyTestBase +public class CovariantReturnMethodTests : DynamicProxyTestBase { - public class ReturnTypeInterceptor : AbstractInterceptorAttribute - { - public override async Task Invoke(AspectContext context, AspectDelegate next) - { - await context.Invoke(next); - - if (context.ReturnValue is BaseResult returnValue) - { - returnValue.Name += nameof(ReturnTypeInterceptor); - } - } - } - - public class BaseResult(string name) - { - public string Name { get; set; } = name; - } - - public class MidResult(string name) : BaseResult(name); - - public class LeafResult(string name) : MidResult(name); - - public interface IService - { - object Property { get; } - object Method(); - - object InterceptedProperty { [ReturnTypeInterceptor] get; } - [ReturnTypeInterceptor] - object InterceptedMethod(); - } - - public class Service : IService - { - public virtual object Property { get; } = nameof(Property); - public virtual object Method() => nameof(Method); - - public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); - [ReturnTypeInterceptor] - public virtual object InterceptedMethod() => nameof(InterceptedMethod); - } - - public class BaseCovariantReturnService : Service - { - public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); - public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); - - public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); - [ReturnTypeInterceptor] - public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); - } - - public class MidCovariantReturnService : BaseCovariantReturnService - { - public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); - public override MidResult Method() => new(nameof(MidCovariantReturnService)); - - public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); - [ReturnTypeInterceptor] - public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); - } - - public class LeafCovariantReturnService : MidCovariantReturnService - { - public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); - public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); - - public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); - [ReturnTypeInterceptor] - public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); - } - /// /// Verifies that an object is exactly the given type (and not a derived type), and that it satisfies the given predicate. /// @@ -111,7 +39,7 @@ public void CreateClassProxy_ForDerivedCovariantReturnType_ShouldUseOverriddenIn [Fact] public void CreateClassProxy_ForBaseServiceAndCovariantImplementation_ShouldUseStringReturnType() { - var service = ProxyGenerator.CreateClassProxy(); + var service = ProxyGenerator.CreateClassProxy(); AssertTypeValue(service.Property, v => v.Name == nameof(BaseCovariantReturnService)); AssertTypeValue(service.Method(), v => v.Name == nameof(BaseCovariantReturnService)); AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); @@ -121,7 +49,7 @@ public void CreateClassProxy_ForBaseServiceAndCovariantImplementation_ShouldUseS [Fact] public void CreateClassProxy_ForBaseServiceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { - var service = ProxyGenerator.CreateClassProxy(); + var service = ProxyGenerator.CreateClassProxy(); AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); @@ -141,7 +69,7 @@ public void CreateClassProxy_ForCovariantServiceAndDerivedImplementation_ShouldU [Fact] public void CreateInterfaceProxy_ForBaseInterfaceAndCovariantImplementation_ShouldUseStringReturnType() { - var service = ProxyGenerator.CreateInterfaceProxy(); + var service = ProxyGenerator.CreateInterfaceProxy(); AssertTypeValue(service.Property, v => v.Name == nameof(BaseCovariantReturnService)); AssertTypeValue(service.Method(), v => v.Name == nameof(BaseCovariantReturnService)); AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); @@ -151,7 +79,7 @@ public void CreateInterfaceProxy_ForBaseInterfaceAndCovariantImplementation_Shou [Fact] public void CreateInterfaceProxy_ForBaseInterfaceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { - var service = ProxyGenerator.CreateInterfaceProxy(); + var service = ProxyGenerator.CreateInterfaceProxy(); AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); diff --git a/tests/AspectCore.Tests/DynamicProxy/ProxyGeneratorTest.cs b/tests/AspectCore.Tests/DynamicProxy/ProxyGeneratorTest.cs index 4d55bed9..5ea7873c 100644 --- a/tests/AspectCore.Tests/DynamicProxy/ProxyGeneratorTest.cs +++ b/tests/AspectCore.Tests/DynamicProxy/ProxyGeneratorTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; using AspectCore.Configuration; @@ -61,6 +61,8 @@ protected override void Configure(IAspectConfiguration configuration) , Predicates.ForService("*BaseService")); } + public interface IService { } + public class Service : F_IService { public Guid Id { get; set; } @@ -80,6 +82,4 @@ public virtual string GetServiceName() } } } - - public interface IService { } } diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs index f2a0aed8..8257e86b 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs @@ -7,125 +7,4 @@ namespace AspectCore.Tests.Extensions; public class TypeExtensionsTests { - // Basic covariant override between classes - private class Animal; - - private class Dog : Animal; - - private class BaseClass - { - public virtual Animal Make() => new(); - } - - private class DerivedClass : BaseClass - { - public override Dog Make() => new(); - } - - [Fact] - public void GetCovariantReturnMethods_FindsBasicCovariantReturnMethod() - { - var methods = typeof(DerivedClass).GetCovariantReturnMethods(); - Assert.Single(methods); - - var info = methods.Single(); - Assert.Equal(nameof(DerivedClass.Make), info.CovariantReturnMethod.Name); - Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); - Assert.Equal(typeof(Animal), info.OverriddenMethod.ReturnType); - Assert.True(info.OverriddenMethod.DeclaringType == typeof(DerivedClass) - || info.OverriddenMethod.DeclaringType == typeof(BaseClass)); - } - - // Multi-level inheritance - private class B1 { public virtual B1 Clone() => new(); } - - private class B2 : B1 { public override B2 Clone() => new(); } - - private class B3 : B2; - - [Fact] - public void GetCovariantReturnMethods_DoesNotReportEmptyForDeeperHierarchy() - { - var methods = typeof(B3).GetCovariantReturnMethods(); - // Should inherit from B2 → B1, so no new covariant method - Assert.Single(methods); - - methods = typeof(B2).GetCovariantReturnMethods(); - Assert.Single(methods); - } - - // Interface with covariant return - private interface IFactory { T Create(); } - - private class Widget; - - private class FancyWidget : Widget; - - private class WidgetFactory : IFactory - { - public virtual Widget Create() => new(); - } - - private class FancyWidgetFactory : WidgetFactory, IFactory - { - public override FancyWidget Create() => new(); - } - - [Fact] - public void GetCovariantReturnMethods_HandlesInterfaceCovariantReturnCorrectly() - { - var info = typeof(FancyWidgetFactory).GetCovariantReturnMethods().Single(); - Assert.Equal(typeof(FancyWidget), info.CovariantReturnMethod.ReturnType); - Assert.Equal(typeof(Widget), info.OverriddenMethod.ReturnType); - // Should list IFactory.Create() as an interface declaration - Assert.Contains(info.InterfaceDeclarations, m => m.DeclaringType!.GetGenericTypeDefinition() == typeof(IFactory<>)); - } - - // Explicit interface implementation - private interface ICreator { T Create(); } - - private class Creator : ICreator - { - Animal ICreator.Create() => new(); - } - - private class DogCreator : Creator, ICreator - { - Dog ICreator.Create() => new(); - } - - [Fact] - public void GetCovariantReturnMethods_FindsExplicitInterfaceImplementation() - { - var infos = typeof(DogCreator).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) - .Where(m => m.Name.Contains("ICreator")) - .SelectMany(m => m.GetInterfaceDeclarations()) - .ToList(); - Assert.NotEmpty(infos); - Assert.All(infos, i => Assert.Equal("Create", i.Name)); - } - - // 5Generic method covariance - private class GenBase { public virtual T Build() => default!; } - - private class GenDerived : GenBase { public override Dog Build() => new(); } - - [Fact] - public void GetCovariantReturnMethods_WorksForGenericCovariantReturn() - { - var info = typeof(GenDerived).GetCovariantReturnMethods().Single(); - Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); - Assert.Empty(info.CovariantReturnMethod.GetGenericArguments()); - Assert.Empty(info.OverriddenMethod.GetGenericArguments()); - Assert.NotEqual(info.OverriddenMethod, info.OverriddenMethod.GetBaseDefinition()); - } - - // Same slot verification - [Fact] - public void IsSameBaseDefinition_CovariantReturn() - { - var m1 = typeof(DerivedClass).GetMethod(nameof(DerivedClass.Make))!; - var m2 = typeof(BaseClass).GetMethod(nameof(BaseClass.Make))!; - Assert.False(m1.IsSameBaseDefinition(m2)); - } } From 1fac7b2f9b63c131e079c8098325c9e4936ba57d Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Mon, 6 Jul 2026 21:38:43 -0230 Subject: [PATCH 29/55] add test cases --- .../AspectCore.Tests/CovariantReturnTypes.cs | 12 ++- .../Extensions/TypeExtensionsTests.cs | 73 ++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/tests/AspectCore.Tests/CovariantReturnTypes.cs b/tests/AspectCore.Tests/CovariantReturnTypes.cs index 8bd09d59..b6723ee5 100644 --- a/tests/AspectCore.Tests/CovariantReturnTypes.cs +++ b/tests/AspectCore.Tests/CovariantReturnTypes.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using AspectCore.DynamicProxy; namespace AspectCore.Tests; @@ -76,4 +76,14 @@ public class LeafCovariantReturnService : MidCovariantReturnService [ReturnTypeInterceptor] public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); } + + public class GenericMethodBaseService + { + public virtual BaseResult Convert(TValue value) => new(nameof(GenericMethodBaseService)); + } + + public class GenericMethodLeafService : GenericMethodBaseService + { + public override LeafResult Convert(TValue value) => new(nameof(GenericMethodLeafService)); + } } diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs index 8257e86b..33604319 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs @@ -1,10 +1,81 @@ -using System.Linq; +using System.Linq; +using System; using System.Reflection; using AspectCore.Extensions; +using AspectCore.Tests; using Xunit; namespace AspectCore.Tests.Extensions; public class TypeExtensionsTests { + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodIsOverriddenWithCovariantReturnType() + { + var method = GetMethod(nameof(CovariantReturnTypes.CommonService.Method), typeof(object)); + var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.BaseCovariantReturnService.Method), typeof(CovariantReturnTypes.BaseResult)); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenMethodNamesDiffer() + { + var method = GetMethod(nameof(CovariantReturnTypes.CommonService.Method), typeof(object)); + var covariantReturnMethod = GetPropertyGetter(nameof(CovariantReturnTypes.BaseCovariantReturnService.Property), typeof(CovariantReturnTypes.BaseResult)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnTypesAreTheSame() + { + var method = GetMethod(nameof(CovariantReturnTypes.BaseCovariantReturnService.Method), typeof(CovariantReturnTypes.BaseResult)); + var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.BaseCovariantReturnService.Method), typeof(CovariantReturnTypes.BaseResult)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnTypeIsNotAssignable() + { + var method = GetMethod(nameof(CovariantReturnTypes.BaseCovariantReturnService.Method), typeof(CovariantReturnTypes.BaseResult)); + var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.CommonService.Method), typeof(object)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenParametersDiffer() + { + var method = GetMethod(nameof(CovariantReturnTypes.CommonService.Method), typeof(object)); + var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.GenericMethodLeafService.Convert), typeof(CovariantReturnTypes.LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodIsOverriddenWithCovariantReturnType() + { + var method = GetMethod(nameof(CovariantReturnTypes.GenericMethodBaseService.Convert), typeof(CovariantReturnTypes.BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.GenericMethodLeafService.Convert), typeof(CovariantReturnTypes.LeafResult), parameterCount: 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + private static MethodInfo GetMethod(string name, Type returnType, int parameterCount = 0) + { + return typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Single(method => + method.Name == name + && method.ReturnType == returnType + && method.GetParameters().Length == parameterCount); + } + + private static MethodInfo GetPropertyGetter(string name, Type propertyType) + { + return typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Single(property => property.Name == name && property.PropertyType == propertyType) + .GetMethod!; + } } From e725632a9263555792553d6bc07b9f5104ed02e6 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Mon, 6 Jul 2026 21:39:13 -0230 Subject: [PATCH 30/55] cleanup --- .../Extensions/TypeExtensionsTests.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs index 33604319..e8802389 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs @@ -1,9 +1,9 @@ -using System.Linq; +using System.Linq; using System; using System.Reflection; using AspectCore.Extensions; -using AspectCore.Tests; using Xunit; +using static AspectCore.Tests.CovariantReturnTypes; namespace AspectCore.Tests.Extensions; @@ -12,8 +12,8 @@ public class TypeExtensionsTests [Fact] public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodIsOverriddenWithCovariantReturnType() { - var method = GetMethod(nameof(CovariantReturnTypes.CommonService.Method), typeof(object)); - var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.BaseCovariantReturnService.Method), typeof(CovariantReturnTypes.BaseResult)); + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var covariantReturnMethod = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -21,8 +21,8 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodIsOve [Fact] public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenMethodNamesDiffer() { - var method = GetMethod(nameof(CovariantReturnTypes.CommonService.Method), typeof(object)); - var covariantReturnMethod = GetPropertyGetter(nameof(CovariantReturnTypes.BaseCovariantReturnService.Property), typeof(CovariantReturnTypes.BaseResult)); + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var covariantReturnMethod = GetPropertyGetter(nameof(BaseCovariantReturnService.Property), typeof(BaseResult)); Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -30,8 +30,8 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenMethodName [Fact] public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnTypesAreTheSame() { - var method = GetMethod(nameof(CovariantReturnTypes.BaseCovariantReturnService.Method), typeof(CovariantReturnTypes.BaseResult)); - var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.BaseCovariantReturnService.Method), typeof(CovariantReturnTypes.BaseResult)); + var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + var covariantReturnMethod = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -39,8 +39,8 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnType [Fact] public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnTypeIsNotAssignable() { - var method = GetMethod(nameof(CovariantReturnTypes.BaseCovariantReturnService.Method), typeof(CovariantReturnTypes.BaseResult)); - var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.CommonService.Method), typeof(object)); + var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + var covariantReturnMethod = GetMethod(nameof(CommonService.Method), typeof(object)); Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -48,8 +48,8 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnType [Fact] public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenParametersDiffer() { - var method = GetMethod(nameof(CovariantReturnTypes.CommonService.Method), typeof(object)); - var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.GenericMethodLeafService.Convert), typeof(CovariantReturnTypes.LeafResult), parameterCount: 1); + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var covariantReturnMethod = GetMethod(nameof(GenericMethodLeafService.Convert), typeof(LeafResult), parameterCount: 1); Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -57,8 +57,8 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenParameters [Fact] public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodIsOverriddenWithCovariantReturnType() { - var method = GetMethod(nameof(CovariantReturnTypes.GenericMethodBaseService.Convert), typeof(CovariantReturnTypes.BaseResult), parameterCount: 1); - var covariantReturnMethod = GetMethod(nameof(CovariantReturnTypes.GenericMethodLeafService.Convert), typeof(CovariantReturnTypes.LeafResult), parameterCount: 1); + var method = GetMethod(nameof(GenericMethodBaseService.Convert), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(GenericMethodLeafService.Convert), typeof(LeafResult), parameterCount: 1); Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } From 862a8c5418afe958161d1ed6f86924233a060cd5 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Mon, 6 Jul 2026 21:52:09 -0230 Subject: [PATCH 31/55] fix generic --- src/AspectCore.Core/Extensions/TypeExtensions.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 2859aeb5..73930b0d 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -159,7 +159,16 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M foreach (var (p1, p2) in params1.Zip(params2)) { - if (p1.ParameterType != p2.ParameterType) + var t1 = p1.ParameterType; + var t2 = p2.ParameterType; + + if (t1 == t2) + continue; + + if (t1.IsGenericParameter == false || t2.IsGenericParameter == false) + return false; + + if (t1.GenericParameterPosition != t2.GenericParameterPosition) return false; } @@ -176,7 +185,7 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M foreach (var (a1, a2) in args1.Zip(args2)) { - if (a1 != a2) + if (a1.GenericParameterPosition != a2.GenericParameterPosition) return false; } } From cb9a5981abe021529e4e4739983fd704273b9914 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 05:55:13 -0230 Subject: [PATCH 32/55] add test cases --- .../Extensions/TypeExtensions.cs | 19 +++++++++++++++ .../AspectCore.Tests/CovariantReturnTypes.cs | 13 ++++++++++- .../Extensions/TypeExtensionsTests.cs | 23 ++++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 73930b0d..5f578c47 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -139,6 +139,12 @@ public static int GetInheritanceDepth(this Type? type) return depth - 1; // 去掉 object 自己那一层 } + /// + /// Determines whether the specified method is overridden by a covariant return method. + /// + /// + /// Assumes it is already a covariant return method. + /// public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, MethodInfo covariantReturnMethod) { if (covariantReturnMethod.Name != method.Name) @@ -168,6 +174,19 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M if (t1.IsGenericParameter == false || t2.IsGenericParameter == false) return false; + var m1 = t1.DeclaringMethod; + var m2 = t2.DeclaringMethod; + + if (m1 is null && m2 is not null + || m1 is not null && m2 is null) + return false; + + if (t1.DeclaringMethod != t2.DeclaringMethod) + return false; + + if (t1.DeclaringType != t2.DeclaringType) + return false; + if (t1.GenericParameterPosition != t2.GenericParameterPosition) return false; } diff --git a/tests/AspectCore.Tests/CovariantReturnTypes.cs b/tests/AspectCore.Tests/CovariantReturnTypes.cs index b6723ee5..65b5303a 100644 --- a/tests/AspectCore.Tests/CovariantReturnTypes.cs +++ b/tests/AspectCore.Tests/CovariantReturnTypes.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using AspectCore.DynamicProxy; namespace AspectCore.Tests; @@ -77,6 +77,17 @@ public class LeafCovariantReturnService : MidCovariantReturnService public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); } + // this class just does common overriding. + public class DerivedLeafCovariantReturnService : LeafCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(DerivedLeafCovariantReturnService)); + public override LeafResult Method() => new(nameof(DerivedLeafCovariantReturnService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(DerivedLeafCovariantReturnService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(DerivedLeafCovariantReturnService)); + } + public class GenericMethodBaseService { public virtual BaseResult Convert(TValue value) => new(nameof(GenericMethodBaseService)); diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs index e8802389..b9124585 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs @@ -1,13 +1,15 @@ using System.Linq; using System; using System.Reflection; +using System.Runtime.CompilerServices; using AspectCore.Extensions; using Xunit; +using Xunit.Abstractions; using static AspectCore.Tests.CovariantReturnTypes; namespace AspectCore.Tests.Extensions; -public class TypeExtensionsTests +public class TypeExtensionsTests(ITestOutputHelper output) { [Fact] public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodIsOverriddenWithCovariantReturnType() @@ -78,4 +80,23 @@ private static MethodInfo GetPropertyGetter(string name, Type propertyType) .Single(property => property.Name == name && property.PropertyType == propertyType) .GetMethod!; } + + [Fact] + public void Print() + { + var methods = typeof(DerivedLeafCovariantReturnService).GetMethods(BindingFlags.Public | BindingFlags.Instance); + foreach (var method in methods) + { + var dt = method.DeclaringType; + if (dt == typeof(object)) + continue; + + var attributes = method.GetCustomAttributesData(); + if (attributes.Any(m => m.AttributeType == typeof(CompilerGeneratedAttribute))) + continue; + + var attributeNames = attributes.Select(a => a.AttributeType.Name); + output.WriteLine($"[{dt?.Name}.{method.Name}] Return Type: {method.ReturnType.Name}, Attributes: {string.Join(", ", attributeNames)}"); + } + } } From 82e10b7520ff65ac483e66873f9015fe8a37a118 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 06:24:23 -0230 Subject: [PATCH 33/55] add test cases --- .../Extensions/MethodInfoExtensions.cs | 11 +- .../Extensions/TypeExtensions.cs | 9 + .../AspectCore.Tests/CovariantReturnTypes.cs | 124 +++++++++ .../Extensions/TypeExtensionsTests.cs | 244 +++++++++++++++++- 4 files changed, 385 insertions(+), 3 deletions(-) diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs index 7d511297..84dd1f05 100644 --- a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection; using static AspectCore.Extensions.TypeExtensions; @@ -80,6 +80,15 @@ public static bool IsSameBaseDefinition(this MethodInfo method, MethodInfo other { return method.GetBaseDefinition() == other.GetBaseDefinition(); } + + public static bool IsConstructedGenericMethod(this MethodInfo method) + { +#if NETSTANDARD2_0 + return method.IsGenericMethod && !method.IsGenericMethodDefinition; +#else + return method.IsConstructedGenericMethod; +#endif + } } } diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 5f578c47..f37997ab 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -150,6 +150,15 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M if (covariantReturnMethod.Name != method.Name) return false; + if (method.IsConstructedGenericMethod()) + { + if (covariantReturnMethod.IsConstructedGenericMethod() == false) + return false; + + method = method.GetGenericMethodDefinition(); + covariantReturnMethod = covariantReturnMethod.GetGenericMethodDefinition(); + } + // return types should not be the same. if (covariantReturnMethod.ReturnType == method.ReturnType) return false; diff --git a/tests/AspectCore.Tests/CovariantReturnTypes.cs b/tests/AspectCore.Tests/CovariantReturnTypes.cs index 65b5303a..8cdbb6f9 100644 --- a/tests/AspectCore.Tests/CovariantReturnTypes.cs +++ b/tests/AspectCore.Tests/CovariantReturnTypes.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using AspectCore.DynamicProxy; @@ -97,4 +98,127 @@ public class GenericMethodLeafService : GenericMethodBaseService { public override LeafResult Convert(TValue value) => new(nameof(GenericMethodLeafService)); } + + public class OrdinaryOverrideBaseService + { + public virtual BaseResult Method() => new(nameof(OrdinaryOverrideBaseService)); + } + + public class OrdinaryOverrideLeafService : OrdinaryOverrideBaseService + { + public override BaseResult Method() => new(nameof(OrdinaryOverrideLeafService)); + } + + public class ParameterBaseService + { + public virtual BaseResult WithBaseParameter(BaseResult value) => value; + + public virtual BaseResult WithTwoParameters(BaseResult value, LeafResult other) => value; + } + + public class ParameterLeafService : ParameterBaseService + { + public override LeafResult WithBaseParameter(BaseResult value) => new(nameof(ParameterLeafService)); + + public override LeafResult WithTwoParameters(BaseResult value, LeafResult other) => new(nameof(ParameterLeafService)); + } + + public class MismatchedParameterLeafService + { + public LeafResult WithBaseParameter(LeafResult value) => value; + } + + public class GenericMethodShapeBaseService + { + public virtual BaseResult Direct(TValue value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult Array(TValue[] value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult List(List value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult Dictionary(Dictionary value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual IEnumerable ReturnList() => []; + } + + public class GenericMethodShapeLeafService : GenericMethodShapeBaseService + { + public override LeafResult Direct(TValue value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult Array(TValue[] value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult List(List value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult Dictionary(Dictionary value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeLeafService)); + + public override List ReturnList() => []; + } + + public class TypeGenericShapeBaseService + { + public virtual BaseResult Direct(TValue value) => new(nameof(TypeGenericShapeBaseService)); + + public virtual BaseResult List(List value) => new(nameof(TypeGenericShapeBaseService)); + + public virtual IEnumerable ReturnList() => []; + } + + public class TypeGenericShapeLeafService : TypeGenericShapeBaseService + { + public override LeafResult Direct(TValue value) => new(nameof(TypeGenericShapeLeafService)); + + public override LeafResult List(List value) => new(nameof(TypeGenericShapeLeafService)); + + public override List ReturnList() => []; + } + + public class MixedGenericShapeBaseService + { + public virtual BaseResult TypeAndMethod(TType typeValue, TMethod methodValue) => new(nameof(MixedGenericShapeBaseService)); + + public virtual BaseResult MethodThenType(TMethod methodValue, TType typeValue) => new(nameof(MixedGenericShapeBaseService)); + } + + public class MixedGenericShapeLeafService : MixedGenericShapeBaseService + { + public override LeafResult TypeAndMethod(TType typeValue, TMethod methodValue) => new(nameof(MixedGenericShapeLeafService)); + + public override LeafResult MethodThenType(TMethod methodValue, TType typeValue) => new(nameof(MixedGenericShapeLeafService)); + } + + public class TypeGenericParameterBaseService + { + public virtual BaseResult Compare(TValue value) => new(nameof(TypeGenericParameterBaseService)); + } + + public class MethodGenericParameterLeafService + { + public LeafResult Compare(TValue value) => new(nameof(MethodGenericParameterLeafService)); + } + + public class GenericPositionZeroBaseService + { + public virtual BaseResult Compare(TFirst value) => new(nameof(GenericPositionZeroBaseService)); + } + + public class GenericPositionOneLeafService + { + public LeafResult Compare(TSecond value) => new(nameof(GenericPositionOneLeafService)); + } + + public class ConstrainedGenericReturnBaseService + { + public virtual BaseResult Create(TValue value) + where TValue : LeafResult + => value; + } + + public class ConstrainedGenericReturnLeafService : ConstrainedGenericReturnBaseService + { + public override TValue Create(TValue value) => value; + } } diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs index b9124585..fbaa79bb 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System; using System.Reflection; using System.Runtime.CompilerServices; @@ -65,15 +65,255 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMeth Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenBothArgumentsAreSameMethodInfoInstance() + { + var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(method)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenBothArgumentsAreSameMethodReflectedFromDifferentTypes() + { + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var reflectedMethod = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(object)); + + Assert.Equal(method.MetadataToken, reflectedMethod.MetadataToken); + Assert.Equal(method.Module, reflectedMethod.Module); + Assert.NotEqual(method.ReflectedType, reflectedMethod.ReflectedType); + Assert.False(method.IsOverriddenByCovariantReturnMethod(reflectedMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverride() + { + var method = GetMethod(nameof(OrdinaryOverrideBaseService.Method), typeof(BaseResult)); + var ordinaryOverrideMethod = GetMethod(nameof(OrdinaryOverrideLeafService.Method), typeof(BaseResult)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(ordinaryOverrideMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverrideOfCovariantLeaf() + { + var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + var ordinaryOverrideMethod = GetMethod(nameof(DerivedLeafCovariantReturnService.Method), typeof(LeafResult)); + + Assert.True(ordinaryOverrideMethod.GetBaseDefinition() == GetMethod(nameof(LeafCovariantReturnService.Method), typeof(LeafResult)).GetBaseDefinition()); + Assert.False(method.IsOverriddenByCovariantReturnMethod(ordinaryOverrideMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenCovariantPropertyGetterOverridesBaseGetter() + { + var method = GetPropertyGetter(nameof(CommonService.Property), typeof(object)); + var covariantReturnMethod = GetPropertyGetter(nameof(BaseCovariantReturnService.Property), typeof(BaseResult)); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenLeafCovariantMethodOverridesObjectReturnMethod() + { + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var covariantReturnMethod = GetMethod(nameof(LeafCovariantReturnService.Method), typeof(LeafResult)); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenLeafCovariantMethodOverridesIntermediateBaseResultMethod() + { + var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + var covariantReturnMethod = GetMethod(nameof(LeafCovariantReturnService.Method), typeof(LeafResult)); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenNonGenericParametersMatch() + { + var method = GetMethod(nameof(ParameterBaseService.WithBaseParameter), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(ParameterLeafService.WithBaseParameter), typeof(LeafResult), parameterCount: 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMultipleNonGenericParametersMatch() + { + var method = GetMethod(nameof(ParameterBaseService.WithTwoParameters), typeof(BaseResult), parameterCount: 2); + var covariantReturnMethod = GetMethod(nameof(ParameterLeafService.WithTwoParameters), typeof(LeafResult), parameterCount: 2); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenParameterTypesDiffer() + { + var method = GetMethod(nameof(ParameterBaseService.WithBaseParameter), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(MismatchedParameterLeafService.WithBaseParameter), typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodParameterIsArrayOfMethodGenericParameter() + { + var method = GetMethod(nameof(GenericMethodShapeBaseService.Array), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(GenericMethodShapeLeafService.Array), typeof(LeafResult), parameterCount: 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodParameterIsListOfMethodGenericParameter() + { + var method = GetMethod(nameof(GenericMethodShapeBaseService.List), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(GenericMethodShapeLeafService.List), typeof(LeafResult), parameterCount: 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodParameterIsDictionaryContainingMethodGenericParameter() + { + var method = GetMethod(nameof(GenericMethodShapeBaseService.Dictionary), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(GenericMethodShapeLeafService.Dictionary), typeof(LeafResult), parameterCount: 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodParameterIsByRefMethodGenericParameter() + { + var method = GetMethod(nameof(GenericMethodShapeBaseService.ByRef), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(GenericMethodShapeLeafService.ByRef), typeof(LeafResult), parameterCount: 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodReturnTypeContainsMethodGenericParameter() + { + var method = GetGenericReturnMethod(nameof(GenericMethodShapeBaseService.ReturnList), typeof(System.Collections.Generic.IEnumerable<>)); + var covariantReturnMethod = GetGenericReturnMethod(nameof(GenericMethodShapeLeafService.ReturnList), typeof(System.Collections.Generic.List<>)); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParameterIsTypeGenericParameter() + { + var method = GetMethod(typeof(TypeGenericShapeBaseService<>), nameof(TypeGenericShapeBaseService.Direct), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(typeof(TypeGenericShapeLeafService<>), nameof(TypeGenericShapeLeafService.Direct), typeof(LeafResult), parameterCount: 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParameterIsListOfTypeGenericParameter() + { + var method = GetMethod(typeof(TypeGenericShapeBaseService<>), nameof(TypeGenericShapeBaseService.List), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(typeof(TypeGenericShapeLeafService<>), nameof(TypeGenericShapeLeafService.List), typeof(LeafResult), parameterCount: 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenReturnTypeContainsTypeGenericParameter() + { + var method = GetGenericReturnMethod(typeof(TypeGenericShapeBaseService<>), nameof(TypeGenericShapeBaseService.ReturnList), typeof(System.Collections.Generic.IEnumerable<>)); + var covariantReturnMethod = GetGenericReturnMethod(typeof(TypeGenericShapeLeafService<>), nameof(TypeGenericShapeLeafService.ReturnList), typeof(System.Collections.Generic.List<>)); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParametersContainTypeAndMethodGenericParameters() + { + var method = GetMethod(typeof(MixedGenericShapeBaseService<>), nameof(MixedGenericShapeBaseService.TypeAndMethod), typeof(BaseResult), parameterCount: 2); + var covariantReturnMethod = GetMethod(typeof(MixedGenericShapeLeafService<>), nameof(MixedGenericShapeLeafService.TypeAndMethod), typeof(LeafResult), parameterCount: 2); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodGenericParameterPrecedesTypeGenericParameter() + { + var method = GetMethod(typeof(MixedGenericShapeBaseService<>), nameof(MixedGenericShapeBaseService.MethodThenType), typeof(BaseResult), parameterCount: 2); + var covariantReturnMethod = GetMethod(typeof(MixedGenericShapeLeafService<>), nameof(MixedGenericShapeLeafService.MethodThenType), typeof(LeafResult), parameterCount: 2); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenTypeGenericParameterIsComparedWithMethodGenericParameter() + { + var method = GetMethod(typeof(TypeGenericParameterBaseService<>), nameof(TypeGenericParameterBaseService.Compare), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(MethodGenericParameterLeafService.Compare), typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenGenericParameterPositionsDiffer() + { + var method = GetMethod(nameof(GenericPositionZeroBaseService.Compare), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(GenericPositionOneLeafService.Compare), typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenCovariantReturnUsesConstrainedGenericParameter() + { + var method = GetMethod(nameof(ConstrainedGenericReturnBaseService.Create), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod( + nameof(ConstrainedGenericReturnLeafService.Create), + method => method.ReturnType.IsGenericParameter && method.GetParameters().Length == 1); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + private static MethodInfo GetMethod(string name, Type returnType, int parameterCount = 0) { - return typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance) + return GetMethod(typeof(T), name, returnType, parameterCount); + } + + private static MethodInfo GetMethod(Type type, string name, Type returnType, int parameterCount = 0) + { + return type.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Single(method => method.Name == name && method.ReturnType == returnType && method.GetParameters().Length == parameterCount); } + private static MethodInfo GetMethod(string name, Func predicate) + { + return GetMethod(typeof(T), name, predicate); + } + + private static MethodInfo GetMethod(Type type, string name, Func predicate) + { + return type.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Single(method => method.Name == name && predicate(method)); + } + + private static MethodInfo GetGenericReturnMethod(string name, Type genericTypeDefinition) + { + return GetGenericReturnMethod(typeof(T), name, genericTypeDefinition); + } + + private static MethodInfo GetGenericReturnMethod(Type type, string name, Type genericTypeDefinition) + { + return GetMethod(type, name, method => + method.ReturnType.IsGenericType + && method.ReturnType.GetGenericTypeDefinition() == genericTypeDefinition); + } + private static MethodInfo GetPropertyGetter(string name, Type propertyType) { return typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) From c6bb5862ea86a4ca69b576b5138b7b074638eb9e Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 06:30:27 -0230 Subject: [PATCH 34/55] move file --- ...OverriddenByCovariantReturnMethodTests.cs} | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) rename tests/AspectCore.Tests/Extensions/{TypeExtensionsTests.cs => TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs} (81%) diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs similarity index 81% rename from tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs rename to tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs index fbaa79bb..cf0cf881 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System; using System.Reflection; using System.Runtime.CompilerServices; @@ -7,12 +7,12 @@ using Xunit.Abstractions; using static AspectCore.Tests.CovariantReturnTypes; -namespace AspectCore.Tests.Extensions; +namespace AspectCore.Tests.Extensions.TypeExtensionsTests; -public class TypeExtensionsTests(ITestOutputHelper output) +public class IsOverriddenByCovariantReturnMethodTests(ITestOutputHelper output) { [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodIsOverriddenWithCovariantReturnType() + public void ShouldReturnTrue_WhenMethodIsOverriddenWithCovariantReturnType() { var method = GetMethod(nameof(CommonService.Method), typeof(object)); var covariantReturnMethod = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); @@ -21,7 +21,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodIsOve } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenMethodNamesDiffer() + public void ShouldReturnFalse_WhenMethodNamesDiffer() { var method = GetMethod(nameof(CommonService.Method), typeof(object)); var covariantReturnMethod = GetPropertyGetter(nameof(BaseCovariantReturnService.Property), typeof(BaseResult)); @@ -30,7 +30,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenMethodName } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnTypesAreTheSame() + public void ShouldReturnFalse_WhenReturnTypesAreTheSame() { var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); var covariantReturnMethod = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); @@ -39,7 +39,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnType } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnTypeIsNotAssignable() + public void ShouldReturnFalse_WhenReturnTypeIsNotAssignable() { var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); var covariantReturnMethod = GetMethod(nameof(CommonService.Method), typeof(object)); @@ -48,7 +48,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenReturnType } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenParametersDiffer() + public void ShouldReturnFalse_WhenParametersDiffer() { var method = GetMethod(nameof(CommonService.Method), typeof(object)); var covariantReturnMethod = GetMethod(nameof(GenericMethodLeafService.Convert), typeof(LeafResult), parameterCount: 1); @@ -57,7 +57,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenParameters } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodIsOverriddenWithCovariantReturnType() + public void ShouldReturnTrue_WhenGenericMethodIsOverriddenWithCovariantReturnType() { var method = GetMethod(nameof(GenericMethodBaseService.Convert), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(GenericMethodLeafService.Convert), typeof(LeafResult), parameterCount: 1); @@ -66,7 +66,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMeth } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenBothArgumentsAreSameMethodInfoInstance() + public void ShouldReturnFalse_WhenBothArgumentsAreSameMethodInfoInstance() { var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); @@ -74,7 +74,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenBothArgume } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenBothArgumentsAreSameMethodReflectedFromDifferentTypes() + public void ShouldReturnFalse_WhenBothArgumentsAreSameMethodReflectedFromDifferentTypes() { var method = GetMethod(nameof(CommonService.Method), typeof(object)); var reflectedMethod = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(object)); @@ -86,7 +86,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenBothArgume } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverride() + public void ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverride() { var method = GetMethod(nameof(OrdinaryOverrideBaseService.Method), typeof(BaseResult)); var ordinaryOverrideMethod = GetMethod(nameof(OrdinaryOverrideLeafService.Method), typeof(BaseResult)); @@ -95,7 +95,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenSecondMeth } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverrideOfCovariantLeaf() + public void ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverrideOfCovariantLeaf() { var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); var ordinaryOverrideMethod = GetMethod(nameof(DerivedLeafCovariantReturnService.Method), typeof(LeafResult)); @@ -105,7 +105,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenSecondMeth } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenCovariantPropertyGetterOverridesBaseGetter() + public void ShouldReturnTrue_WhenCovariantPropertyGetterOverridesBaseGetter() { var method = GetPropertyGetter(nameof(CommonService.Property), typeof(object)); var covariantReturnMethod = GetPropertyGetter(nameof(BaseCovariantReturnService.Property), typeof(BaseResult)); @@ -114,7 +114,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenCovariantPr } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenLeafCovariantMethodOverridesObjectReturnMethod() + public void ShouldReturnTrue_WhenLeafCovariantMethodOverridesObjectReturnMethod() { var method = GetMethod(nameof(CommonService.Method), typeof(object)); var covariantReturnMethod = GetMethod(nameof(LeafCovariantReturnService.Method), typeof(LeafResult)); @@ -123,7 +123,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenLeafCovaria } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenLeafCovariantMethodOverridesIntermediateBaseResultMethod() + public void ShouldReturnTrue_WhenLeafCovariantMethodOverridesIntermediateBaseResultMethod() { var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); var covariantReturnMethod = GetMethod(nameof(LeafCovariantReturnService.Method), typeof(LeafResult)); @@ -132,7 +132,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenLeafCovaria } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenNonGenericParametersMatch() + public void ShouldReturnTrue_WhenNonGenericParametersMatch() { var method = GetMethod(nameof(ParameterBaseService.WithBaseParameter), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(ParameterLeafService.WithBaseParameter), typeof(LeafResult), parameterCount: 1); @@ -141,7 +141,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenNonGenericP } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMultipleNonGenericParametersMatch() + public void ShouldReturnTrue_WhenMultipleNonGenericParametersMatch() { var method = GetMethod(nameof(ParameterBaseService.WithTwoParameters), typeof(BaseResult), parameterCount: 2); var covariantReturnMethod = GetMethod(nameof(ParameterLeafService.WithTwoParameters), typeof(LeafResult), parameterCount: 2); @@ -150,7 +150,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMultipleNon } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenParameterTypesDiffer() + public void ShouldReturnFalse_WhenParameterTypesDiffer() { var method = GetMethod(nameof(ParameterBaseService.WithBaseParameter), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(MismatchedParameterLeafService.WithBaseParameter), typeof(LeafResult), parameterCount: 1); @@ -159,7 +159,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenParameterT } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodParameterIsArrayOfMethodGenericParameter() + public void ShouldReturnTrue_WhenGenericMethodParameterIsArrayOfMethodGenericParameter() { var method = GetMethod(nameof(GenericMethodShapeBaseService.Array), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(GenericMethodShapeLeafService.Array), typeof(LeafResult), parameterCount: 1); @@ -168,7 +168,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMeth } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodParameterIsListOfMethodGenericParameter() + public void ShouldReturnTrue_WhenGenericMethodParameterIsListOfMethodGenericParameter() { var method = GetMethod(nameof(GenericMethodShapeBaseService.List), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(GenericMethodShapeLeafService.List), typeof(LeafResult), parameterCount: 1); @@ -177,7 +177,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMeth } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodParameterIsDictionaryContainingMethodGenericParameter() + public void ShouldReturnTrue_WhenGenericMethodParameterIsDictionaryContainingMethodGenericParameter() { var method = GetMethod(nameof(GenericMethodShapeBaseService.Dictionary), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(GenericMethodShapeLeafService.Dictionary), typeof(LeafResult), parameterCount: 1); @@ -186,7 +186,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMeth } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodParameterIsByRefMethodGenericParameter() + public void ShouldReturnTrue_WhenGenericMethodParameterIsByRefMethodGenericParameter() { var method = GetMethod(nameof(GenericMethodShapeBaseService.ByRef), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(GenericMethodShapeLeafService.ByRef), typeof(LeafResult), parameterCount: 1); @@ -195,7 +195,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMeth } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMethodReturnTypeContainsMethodGenericParameter() + public void ShouldReturnTrue_WhenGenericMethodReturnTypeContainsMethodGenericParameter() { var method = GetGenericReturnMethod(nameof(GenericMethodShapeBaseService.ReturnList), typeof(System.Collections.Generic.IEnumerable<>)); var covariantReturnMethod = GetGenericReturnMethod(nameof(GenericMethodShapeLeafService.ReturnList), typeof(System.Collections.Generic.List<>)); @@ -204,7 +204,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenGenericMeth } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParameterIsTypeGenericParameter() + public void ShouldReturnTrue_WhenParameterIsTypeGenericParameter() { var method = GetMethod(typeof(TypeGenericShapeBaseService<>), nameof(TypeGenericShapeBaseService.Direct), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(typeof(TypeGenericShapeLeafService<>), nameof(TypeGenericShapeLeafService.Direct), typeof(LeafResult), parameterCount: 1); @@ -213,7 +213,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParameterIs } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParameterIsListOfTypeGenericParameter() + public void ShouldReturnTrue_WhenParameterIsListOfTypeGenericParameter() { var method = GetMethod(typeof(TypeGenericShapeBaseService<>), nameof(TypeGenericShapeBaseService.List), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(typeof(TypeGenericShapeLeafService<>), nameof(TypeGenericShapeLeafService.List), typeof(LeafResult), parameterCount: 1); @@ -222,7 +222,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParameterIs } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenReturnTypeContainsTypeGenericParameter() + public void ShouldReturnTrue_WhenReturnTypeContainsTypeGenericParameter() { var method = GetGenericReturnMethod(typeof(TypeGenericShapeBaseService<>), nameof(TypeGenericShapeBaseService.ReturnList), typeof(System.Collections.Generic.IEnumerable<>)); var covariantReturnMethod = GetGenericReturnMethod(typeof(TypeGenericShapeLeafService<>), nameof(TypeGenericShapeLeafService.ReturnList), typeof(System.Collections.Generic.List<>)); @@ -231,7 +231,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenReturnTypeC } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParametersContainTypeAndMethodGenericParameters() + public void ShouldReturnTrue_WhenParametersContainTypeAndMethodGenericParameters() { var method = GetMethod(typeof(MixedGenericShapeBaseService<>), nameof(MixedGenericShapeBaseService.TypeAndMethod), typeof(BaseResult), parameterCount: 2); var covariantReturnMethod = GetMethod(typeof(MixedGenericShapeLeafService<>), nameof(MixedGenericShapeLeafService.TypeAndMethod), typeof(LeafResult), parameterCount: 2); @@ -240,7 +240,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenParametersC } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodGenericParameterPrecedesTypeGenericParameter() + public void ShouldReturnTrue_WhenMethodGenericParameterPrecedesTypeGenericParameter() { var method = GetMethod(typeof(MixedGenericShapeBaseService<>), nameof(MixedGenericShapeBaseService.MethodThenType), typeof(BaseResult), parameterCount: 2); var covariantReturnMethod = GetMethod(typeof(MixedGenericShapeLeafService<>), nameof(MixedGenericShapeLeafService.MethodThenType), typeof(LeafResult), parameterCount: 2); @@ -249,7 +249,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenMethodGener } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenTypeGenericParameterIsComparedWithMethodGenericParameter() + public void ShouldReturnFalse_WhenTypeGenericParameterIsComparedWithMethodGenericParameter() { var method = GetMethod(typeof(TypeGenericParameterBaseService<>), nameof(TypeGenericParameterBaseService.Compare), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(MethodGenericParameterLeafService.Compare), typeof(LeafResult), parameterCount: 1); @@ -258,7 +258,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenTypeGeneri } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenGenericParameterPositionsDiffer() + public void ShouldReturnFalse_WhenGenericParameterPositionsDiffer() { var method = GetMethod(nameof(GenericPositionZeroBaseService.Compare), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(nameof(GenericPositionOneLeafService.Compare), typeof(LeafResult), parameterCount: 1); @@ -267,7 +267,7 @@ public void IsOverriddenByCovariantReturnMethod_ShouldReturnFalse_WhenGenericPar } [Fact] - public void IsOverriddenByCovariantReturnMethod_ShouldReturnTrue_WhenCovariantReturnUsesConstrainedGenericParameter() + public void ShouldReturnTrue_WhenCovariantReturnUsesConstrainedGenericParameter() { var method = GetMethod(nameof(ConstrainedGenericReturnBaseService.Create), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod( From 5fc9a3623b0475cfda4e6d94de3f079e795f76e7 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 06:58:14 -0230 Subject: [PATCH 35/55] refine types --- tests/AspectCore.Tests/Classes.cs | 11 +- .../AspectCore.Tests/CovariantReturnTypes.cs | 157 +--------------- ...sOverriddenByCovariantReturnMethodTests.cs | 12 +- .../TypeExtensionsTests/TestTypes.cs | 175 ++++++++++++++++++ 4 files changed, 200 insertions(+), 155 deletions(-) create mode 100644 tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs diff --git a/tests/AspectCore.Tests/Classes.cs b/tests/AspectCore.Tests/Classes.cs index 98764726..5d1615f1 100644 --- a/tests/AspectCore.Tests/Classes.cs +++ b/tests/AspectCore.Tests/Classes.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using AspectCore.DynamicProxy; using AspectCore.DependencyInjection; @@ -274,4 +274,13 @@ public bool OutInt(out int num) return true; } } + + public class BaseResult(string name) + { + public string Name { get; set; } = name; + } + + public class MidResult(string name) : BaseResult(name); + + public class LeafResult(string name) : MidResult(name); } \ No newline at end of file diff --git a/tests/AspectCore.Tests/CovariantReturnTypes.cs b/tests/AspectCore.Tests/CovariantReturnTypes.cs index 8cdbb6f9..e29ac8b8 100644 --- a/tests/AspectCore.Tests/CovariantReturnTypes.cs +++ b/tests/AspectCore.Tests/CovariantReturnTypes.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using AspectCore.DynamicProxy; @@ -19,15 +19,6 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) } } - public class BaseResult(string name) - { - public string Name { get; set; } = name; - } - - public class MidResult(string name) : BaseResult(name); - - public class LeafResult(string name) : MidResult(name); - public interface ICommonService { object Property { get; } @@ -78,147 +69,17 @@ public class LeafCovariantReturnService : MidCovariantReturnService public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); } - // this class just does common overriding. - public class DerivedLeafCovariantReturnService : LeafCovariantReturnService + // this class just does ordinary overriding. + public class OrdinaryOverrideService : LeafCovariantReturnService { - public override LeafResult Property { get; } = new(nameof(DerivedLeafCovariantReturnService)); - public override LeafResult Method() => new(nameof(DerivedLeafCovariantReturnService)); + public override LeafResult Property { get; } = new(nameof(OrdinaryOverrideService)); + public override LeafResult Method() => new(nameof(OrdinaryOverrideService)); - public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(DerivedLeafCovariantReturnService)); + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(OrdinaryOverrideService)); [ReturnTypeInterceptor] - public override LeafResult InterceptedMethod() => new(nameof(DerivedLeafCovariantReturnService)); - } - - public class GenericMethodBaseService - { - public virtual BaseResult Convert(TValue value) => new(nameof(GenericMethodBaseService)); - } - - public class GenericMethodLeafService : GenericMethodBaseService - { - public override LeafResult Convert(TValue value) => new(nameof(GenericMethodLeafService)); - } - - public class OrdinaryOverrideBaseService - { - public virtual BaseResult Method() => new(nameof(OrdinaryOverrideBaseService)); - } - - public class OrdinaryOverrideLeafService : OrdinaryOverrideBaseService - { - public override BaseResult Method() => new(nameof(OrdinaryOverrideLeafService)); - } - - public class ParameterBaseService - { - public virtual BaseResult WithBaseParameter(BaseResult value) => value; - - public virtual BaseResult WithTwoParameters(BaseResult value, LeafResult other) => value; - } - - public class ParameterLeafService : ParameterBaseService - { - public override LeafResult WithBaseParameter(BaseResult value) => new(nameof(ParameterLeafService)); - - public override LeafResult WithTwoParameters(BaseResult value, LeafResult other) => new(nameof(ParameterLeafService)); - } - - public class MismatchedParameterLeafService - { - public LeafResult WithBaseParameter(LeafResult value) => value; - } - - public class GenericMethodShapeBaseService - { - public virtual BaseResult Direct(TValue value) => new(nameof(GenericMethodShapeBaseService)); - - public virtual BaseResult Array(TValue[] value) => new(nameof(GenericMethodShapeBaseService)); - - public virtual BaseResult List(List value) => new(nameof(GenericMethodShapeBaseService)); - - public virtual BaseResult Dictionary(Dictionary value) => new(nameof(GenericMethodShapeBaseService)); - - public virtual BaseResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeBaseService)); - - public virtual IEnumerable ReturnList() => []; + public override LeafResult InterceptedMethod() => new(nameof(OrdinaryOverrideService)); } - public class GenericMethodShapeLeafService : GenericMethodShapeBaseService - { - public override LeafResult Direct(TValue value) => new(nameof(GenericMethodShapeLeafService)); - - public override LeafResult Array(TValue[] value) => new(nameof(GenericMethodShapeLeafService)); - - public override LeafResult List(List value) => new(nameof(GenericMethodShapeLeafService)); - - public override LeafResult Dictionary(Dictionary value) => new(nameof(GenericMethodShapeLeafService)); - - public override LeafResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeLeafService)); - - public override List ReturnList() => []; - } - - public class TypeGenericShapeBaseService - { - public virtual BaseResult Direct(TValue value) => new(nameof(TypeGenericShapeBaseService)); - - public virtual BaseResult List(List value) => new(nameof(TypeGenericShapeBaseService)); - - public virtual IEnumerable ReturnList() => []; - } - - public class TypeGenericShapeLeafService : TypeGenericShapeBaseService - { - public override LeafResult Direct(TValue value) => new(nameof(TypeGenericShapeLeafService)); - - public override LeafResult List(List value) => new(nameof(TypeGenericShapeLeafService)); - - public override List ReturnList() => []; - } - - public class MixedGenericShapeBaseService - { - public virtual BaseResult TypeAndMethod(TType typeValue, TMethod methodValue) => new(nameof(MixedGenericShapeBaseService)); - - public virtual BaseResult MethodThenType(TMethod methodValue, TType typeValue) => new(nameof(MixedGenericShapeBaseService)); - } - - public class MixedGenericShapeLeafService : MixedGenericShapeBaseService - { - public override LeafResult TypeAndMethod(TType typeValue, TMethod methodValue) => new(nameof(MixedGenericShapeLeafService)); - - public override LeafResult MethodThenType(TMethod methodValue, TType typeValue) => new(nameof(MixedGenericShapeLeafService)); - } - - public class TypeGenericParameterBaseService - { - public virtual BaseResult Compare(TValue value) => new(nameof(TypeGenericParameterBaseService)); - } - - public class MethodGenericParameterLeafService - { - public LeafResult Compare(TValue value) => new(nameof(MethodGenericParameterLeafService)); - } - - public class GenericPositionZeroBaseService - { - public virtual BaseResult Compare(TFirst value) => new(nameof(GenericPositionZeroBaseService)); - } - - public class GenericPositionOneLeafService - { - public LeafResult Compare(TSecond value) => new(nameof(GenericPositionOneLeafService)); - } - - public class ConstrainedGenericReturnBaseService - { - public virtual BaseResult Create(TValue value) - where TValue : LeafResult - => value; - } - - public class ConstrainedGenericReturnLeafService : ConstrainedGenericReturnBaseService - { - public override TValue Create(TValue value) => value; - } + // this class just inherits from OrdinaryOverrideService, and does not override any members. + public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; } diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs index cf0cf881..c7879fcc 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs @@ -5,7 +5,7 @@ using AspectCore.Extensions; using Xunit; using Xunit.Abstractions; -using static AspectCore.Tests.CovariantReturnTypes; +using static AspectCore.Tests.Extensions.TypeExtensionsTests.TestTypes; namespace AspectCore.Tests.Extensions.TypeExtensionsTests; @@ -88,8 +88,8 @@ public void ShouldReturnFalse_WhenBothArgumentsAreSameMethodReflectedFromDiffere [Fact] public void ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverride() { - var method = GetMethod(nameof(OrdinaryOverrideBaseService.Method), typeof(BaseResult)); - var ordinaryOverrideMethod = GetMethod(nameof(OrdinaryOverrideLeafService.Method), typeof(BaseResult)); + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var ordinaryOverrideMethod = GetMethod(nameof(OrdinaryOverrideService.Method), typeof(object)); Assert.False(method.IsOverriddenByCovariantReturnMethod(ordinaryOverrideMethod)); } @@ -97,8 +97,8 @@ public void ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverride() [Fact] public void ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverrideOfCovariantLeaf() { - var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); - var ordinaryOverrideMethod = GetMethod(nameof(DerivedLeafCovariantReturnService.Method), typeof(LeafResult)); + var method = GetMethod(nameof(LeafCovariantReturnService.Method), typeof(BaseResult)); + var ordinaryOverrideMethod = GetMethod(nameof(OrdinaryOverrideLeafService.Method), typeof(LeafResult)); Assert.True(ordinaryOverrideMethod.GetBaseDefinition() == GetMethod(nameof(LeafCovariantReturnService.Method), typeof(LeafResult)).GetBaseDefinition()); Assert.False(method.IsOverriddenByCovariantReturnMethod(ordinaryOverrideMethod)); @@ -324,7 +324,7 @@ private static MethodInfo GetPropertyGetter(string name, Type propertyType) [Fact] public void Print() { - var methods = typeof(DerivedLeafCovariantReturnService).GetMethods(BindingFlags.Public | BindingFlags.Instance); + var methods = typeof(DerivedOrdinaryOverrideLeafService).GetMethods(BindingFlags.Public | BindingFlags.Instance); foreach (var method in methods) { var dt = method.DeclaringType; diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs new file mode 100644 index 00000000..901be1bc --- /dev/null +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs @@ -0,0 +1,175 @@ +using System.Collections.Generic; + +namespace AspectCore.Tests.Extensions.TypeExtensionsTests; + +public class TestTypes +{ + public interface ICommonService + { + object Property { get; } + object Method(); + } + + public class CommonService : ICommonService + { + public virtual object Property { get; } = nameof(Property); + public virtual object Method() => nameof(Method); + } + + public class OrdinaryOverrideService : CommonService + { + public override object Property { get; } = new BaseResult(nameof(OrdinaryOverrideService)); + public override object Method() => new BaseResult(nameof(OrdinaryOverrideService)); + } + + public class BaseCovariantReturnService : CommonService + { + public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); + public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); + } + + public class MidCovariantReturnService : BaseCovariantReturnService + { + public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); + public override MidResult Method() => new(nameof(MidCovariantReturnService)); + } + + public class LeafCovariantReturnService : MidCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); + public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); + } + + public class DerivedLeafCovariantReturnService : LeafCovariantReturnService; + + public class OrdinaryOverrideLeafService : LeafCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(OrdinaryOverrideLeafService)); + public override LeafResult Method() => new(nameof(OrdinaryOverrideLeafService)); + } + + public class DerivedOrdinaryOverrideLeafService : OrdinaryOverrideLeafService; + + public class GenericMethodBaseService + { + public virtual BaseResult Convert(TValue value) => new(nameof(GenericMethodBaseService)); + } + + public class GenericMethodLeafService : GenericMethodBaseService + { + public override LeafResult Convert(TValue value) => new(nameof(GenericMethodLeafService)); + } + + public class ParameterBaseService + { + public virtual BaseResult WithBaseParameter(BaseResult value) => value; + + public virtual BaseResult WithTwoParameters(BaseResult value, LeafResult other) => value; + } + + public class ParameterLeafService : ParameterBaseService + { + public override LeafResult WithBaseParameter(BaseResult value) => new(nameof(ParameterLeafService)); + + public override LeafResult WithTwoParameters(BaseResult value, LeafResult other) => new(nameof(ParameterLeafService)); + } + + public class MismatchedParameterLeafService + { + public LeafResult WithBaseParameter(LeafResult value) => value; + } + + public class GenericMethodShapeBaseService + { + public virtual BaseResult Direct(TValue value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult Array(TValue[] value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult List(List value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult Dictionary(Dictionary value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual IEnumerable ReturnList() => []; + } + + public class GenericMethodShapeLeafService : GenericMethodShapeBaseService + { + public override LeafResult Direct(TValue value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult Array(TValue[] value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult List(List value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult Dictionary(Dictionary value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeLeafService)); + + public override List ReturnList() => []; + } + + public class TypeGenericShapeBaseService + { + public virtual BaseResult Direct(TValue value) => new(nameof(TypeGenericShapeBaseService)); + + public virtual BaseResult List(List value) => new(nameof(TypeGenericShapeBaseService)); + + public virtual IEnumerable ReturnList() => []; + } + + public class TypeGenericShapeLeafService : TypeGenericShapeBaseService + { + public override LeafResult Direct(TValue value) => new(nameof(TypeGenericShapeLeafService)); + + public override LeafResult List(List value) => new(nameof(TypeGenericShapeLeafService)); + + public override List ReturnList() => []; + } + + public class MixedGenericShapeBaseService + { + public virtual BaseResult TypeAndMethod(TType typeValue, TMethod methodValue) => new(nameof(MixedGenericShapeBaseService)); + + public virtual BaseResult MethodThenType(TMethod methodValue, TType typeValue) => new(nameof(MixedGenericShapeBaseService)); + } + + public class MixedGenericShapeLeafService : MixedGenericShapeBaseService + { + public override LeafResult TypeAndMethod(TType typeValue, TMethod methodValue) => new(nameof(MixedGenericShapeLeafService)); + + public override LeafResult MethodThenType(TMethod methodValue, TType typeValue) => new(nameof(MixedGenericShapeLeafService)); + } + + public class TypeGenericParameterBaseService + { + public virtual BaseResult Compare(TValue value) => new(nameof(TypeGenericParameterBaseService)); + } + + public class MethodGenericParameterLeafService + { + public LeafResult Compare(TValue value) => new(nameof(MethodGenericParameterLeafService)); + } + + public class GenericPositionZeroBaseService + { + public virtual BaseResult Compare(TFirst value) => new(nameof(GenericPositionZeroBaseService)); + } + + public class GenericPositionOneLeafService + { + public LeafResult Compare(TSecond value) => new(nameof(GenericPositionOneLeafService)); + } + + public class ConstrainedGenericReturnBaseService + { + public virtual BaseResult Create(TValue value) + where TValue : LeafResult + => value; + } + + public class ConstrainedGenericReturnLeafService : ConstrainedGenericReturnBaseService + { + public override TValue Create(TValue value) => value; + } +} From 2a2275af576de0e1b07e77eedbb60cd959e513b9 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 07:04:36 -0230 Subject: [PATCH 36/55] refine tests --- .../AspectCore.Tests/CovariantReturnTypes.cs | 85 ------------------ ...odTests.cs => CovariantReturnTypeTests.cs} | 86 ++++++++++++++++++- 2 files changed, 84 insertions(+), 87 deletions(-) delete mode 100644 tests/AspectCore.Tests/CovariantReturnTypes.cs rename tests/AspectCore.Tests/DynamicProxy/{CovariantReturnMethodTests.cs => CovariantReturnTypeTests.cs} (61%) diff --git a/tests/AspectCore.Tests/CovariantReturnTypes.cs b/tests/AspectCore.Tests/CovariantReturnTypes.cs deleted file mode 100644 index e29ac8b8..00000000 --- a/tests/AspectCore.Tests/CovariantReturnTypes.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using AspectCore.DynamicProxy; - -namespace AspectCore.Tests; - -public class CovariantReturnTypes -{ - public class ReturnTypeInterceptor : AbstractInterceptorAttribute - { - public override async Task Invoke(AspectContext context, AspectDelegate next) - { - await context.Invoke(next); - - if (context.ReturnValue is BaseResult returnValue) - { - returnValue.Name += nameof(ReturnTypeInterceptor); - } - } - } - - public interface ICommonService - { - object Property { get; } - object Method(); - - object InterceptedProperty { [ReturnTypeInterceptor] get; } - [ReturnTypeInterceptor] - object InterceptedMethod(); - } - - public class CommonService : ICommonService - { - public virtual object Property { get; } = nameof(Property); - public virtual object Method() => nameof(Method); - - public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); - [ReturnTypeInterceptor] - public virtual object InterceptedMethod() => nameof(InterceptedMethod); - } - - public class BaseCovariantReturnService : CommonService - { - public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); - public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); - - public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); - [ReturnTypeInterceptor] - public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); - } - - public class MidCovariantReturnService : BaseCovariantReturnService - { - public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); - public override MidResult Method() => new(nameof(MidCovariantReturnService)); - - public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); - [ReturnTypeInterceptor] - public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); - } - - public class LeafCovariantReturnService : MidCovariantReturnService - { - public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); - public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); - - public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); - [ReturnTypeInterceptor] - public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); - } - - // this class just does ordinary overriding. - public class OrdinaryOverrideService : LeafCovariantReturnService - { - public override LeafResult Property { get; } = new(nameof(OrdinaryOverrideService)); - public override LeafResult Method() => new(nameof(OrdinaryOverrideService)); - - public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(OrdinaryOverrideService)); - [ReturnTypeInterceptor] - public override LeafResult InterceptedMethod() => new(nameof(OrdinaryOverrideService)); - } - - // this class just inherits from OrdinaryOverrideService, and does not override any members. - public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; -} diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs similarity index 61% rename from tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs rename to tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs index c8fa97a5..42b007d1 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs @@ -1,11 +1,11 @@ using System; +using System.Threading.Tasks; using AspectCore.DynamicProxy; using Xunit; -using static AspectCore.Tests.CovariantReturnTypes; namespace AspectCore.Tests.DynamicProxy; -public class CovariantReturnMethodTests : DynamicProxyTestBase +public partial class CovariantReturnTypeTests : DynamicProxyTestBase { /// /// Verifies that an object is exactly the given type (and not a derived type), and that it satisfies the given predicate. @@ -86,3 +86,85 @@ public void CreateInterfaceProxy_ForBaseInterfaceAndDerivedImplementation_Should AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); } } + +// define a partial class to hold the test types. +partial class CovariantReturnTypeTests +{ + public class ReturnTypeInterceptor : AbstractInterceptorAttribute + { + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + await context.Invoke(next); + + if (context.ReturnValue is BaseResult returnValue) + { + returnValue.Name += nameof(ReturnTypeInterceptor); + } + } + } + + public interface ICommonService + { + object Property { get; } + object Method(); + + object InterceptedProperty { [ReturnTypeInterceptor] get; } + [ReturnTypeInterceptor] + object InterceptedMethod(); + } + + public class CommonService : ICommonService + { + public virtual object Property { get; } = nameof(Property); + public virtual object Method() => nameof(Method); + + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); + [ReturnTypeInterceptor] + public virtual object InterceptedMethod() => nameof(InterceptedMethod); + } + + public class BaseCovariantReturnService : CommonService + { + public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); + public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); + + public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); + [ReturnTypeInterceptor] + public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); + } + + public class MidCovariantReturnService : BaseCovariantReturnService + { + public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); + public override MidResult Method() => new(nameof(MidCovariantReturnService)); + + public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); + [ReturnTypeInterceptor] + public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); + } + + public class LeafCovariantReturnService : MidCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); + public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); + } + + // this class just does ordinary overriding. + public class OrdinaryOverrideService : LeafCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(OrdinaryOverrideService)); + public override LeafResult Method() => new(nameof(OrdinaryOverrideService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(OrdinaryOverrideService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(OrdinaryOverrideService)); + } + + // this class just inherits from OrdinaryOverrideService, and does not override any members. + public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; +} + From e7306fe01f30b25c4707f1f0bdf591dedf93f58c Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 13:27:39 -0230 Subject: [PATCH 37/55] fix TypeExtensions --- .../Extensions/ObjectExtensions.cs | 16 + .../Extensions/TypeExtensions.cs | 419 +++++++++++------- ...sOverriddenByCovariantReturnMethodTests.cs | 57 ++- .../TypeExtensionsTests/TestTypes.cs | 1 + 4 files changed, 314 insertions(+), 179 deletions(-) create mode 100644 src/AspectCore.Core/Extensions/ObjectExtensions.cs diff --git a/src/AspectCore.Core/Extensions/ObjectExtensions.cs b/src/AspectCore.Core/Extensions/ObjectExtensions.cs new file mode 100644 index 00000000..59762d5b --- /dev/null +++ b/src/AspectCore.Core/Extensions/ObjectExtensions.cs @@ -0,0 +1,16 @@ +#nullable enable +namespace AspectCore.Core.Extensions; + +internal static class ObjectExtensions +{ + /// + /// Determines whether the null state of two objects is identical. + /// + /// The first object to compare. + /// The second object to compare. + /// Returns true if both parameters are null or both are not null; otherwise, false. + public static bool IsSameNullState(this T? a, T? b) + { + return (a is null) == (b is null); + } +} diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index f37997ab..55dc41f4 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -3,222 +3,301 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using AspectCore.Core.Extensions; // ReSharper disable once CheckNamespace -namespace AspectCore.Extensions +namespace AspectCore.Extensions; + +internal readonly struct CovariantReturnMethodInfo { - internal readonly struct CovariantReturnMethodInfo + /// + /// The method that defines the covariant return type — + /// i.e., the overriding method that returns a more derived type. + /// + public readonly MethodInfo CovariantReturnMethod; + + /// + /// Gets the inheritance depth of the type that declares the . + /// + public readonly int InheritanceDepth; + + /// + /// Gets the method that is overridden or implemented by . + /// + /// + /// This is **reflected from the derived type**, not necessarily + /// the base definition returned by . + /// + /// In other words, it represents the version of the base or interface method as seen + /// through the derived class’s reflection context, which may differ from the canonical + /// base definition when covariant return types are involved. + /// + /// + public readonly MethodInfo OverriddenMethod; + + /// + /// The set of interface method declarations (if any) + /// that are implemented by the . + /// + public readonly HashSet InterfaceDeclarations; + + public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo overriddenMethod, HashSet interfaceDeclarations) { - /// - /// The method that defines the covariant return type — - /// i.e., the overriding method that returns a more derived type. - /// - public readonly MethodInfo CovariantReturnMethod; - - /// - /// Gets the inheritance depth of the type that declares the . - /// - public readonly int InheritanceDepth; - - /// - /// Gets the method that is overridden or implemented by . - /// - /// - /// This is **reflected from the derived type**, not necessarily - /// the base definition returned by . - /// - /// In other words, it represents the version of the base or interface method as seen - /// through the derived class’s reflection context, which may differ from the canonical - /// base definition when covariant return types are involved. - /// - /// - public readonly MethodInfo OverriddenMethod; - - /// - /// The set of interface method declarations (if any) - /// that are implemented by the . - /// - public readonly HashSet InterfaceDeclarations; - - public CovariantReturnMethodInfo(MethodInfo covariantReturnMethod, MethodInfo overriddenMethod, HashSet interfaceDeclarations) - { - InterfaceDeclarations = interfaceDeclarations; - OverriddenMethod = overriddenMethod; - CovariantReturnMethod = covariantReturnMethod; - InheritanceDepth = covariantReturnMethod.DeclaringType.GetInheritanceDepth(); - } + InterfaceDeclarations = interfaceDeclarations; + OverriddenMethod = overriddenMethod; + CovariantReturnMethod = covariantReturnMethod; + InheritanceDepth = covariantReturnMethod.DeclaringType.GetInheritanceDepth(); } +} - internal static class TypeExtensions +internal static class TypeExtensions +{ + public static readonly Type? PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); + + /// + /// Finds methods participating in covariant-return overrides on the specified type + /// and matches them with their non-covariant overridden methods. + /// + /// + /// The type whose methods should be inspected. + /// + /// + /// A collection of containing: + /// + /// + /// + /// The covariant-return method. + /// + /// + /// + /// + /// The corresponding overridden method with the original return type. + /// + /// + /// + /// + /// The interface methods implemented by the covariant-return method. + /// + /// + /// + /// Returns an empty collection if the current runtime does not support + /// covariant return types. + /// + public static IReadOnlyList GetCovariantReturnMethods(this Type type) { - public static readonly Type? PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false); - - /// - /// Finds methods participating in covariant-return overrides on the specified type - /// and matches them with their non-covariant overridden methods. - /// - /// - /// The type whose methods should be inspected. - /// - /// - /// A collection of containing: - /// - /// - /// - /// The covariant-return method. - /// - /// - /// - /// - /// The corresponding overridden method with the original return type. - /// - /// - /// - /// - /// The interface methods implemented by the covariant-return method. - /// - /// - /// - /// Returns an empty collection if the current runtime does not support - /// covariant return types. - /// - public static IReadOnlyList GetCovariantReturnMethods(this Type type) + var result = new List(); + // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. + if (PreserveBaseOverridesAttribute is null) + return result; + + var methods = type + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .GroupBy(m => m.IsInCovariantReturnChain()) + .ToDictionary(m => m.Key, m => m.ToArray()); + + var covariantReturnMethods = methods.GetValueOrDefault(true, Array.Empty()); + var otherMethods = methods.GetValueOrDefault(false, Array.Empty()); + + foreach (var covariantReturnMethod in covariantReturnMethods) { - var result = new List(); - // No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types. - if (PreserveBaseOverridesAttribute is null) - return result; + var overriddenMethod = otherMethods.FirstOrDefault(m => m.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + if (overriddenMethod is null) + continue; - var methods = type - .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .GroupBy(m => m.IsInCovariantReturnChain()) - .ToDictionary(m => m.Key, m => m.ToArray()); + var interfaceDeclarations = covariantReturnMethod.GetInterfaceDeclarations().ToHashSet(); + result.Add(new CovariantReturnMethodInfo(covariantReturnMethod, overriddenMethod, interfaceDeclarations)); + } - var covariantReturnMethods = methods.GetValueOrDefault(true, Array.Empty()); - var otherMethods = methods.GetValueOrDefault(false, Array.Empty()); + return result; + } - foreach (var covariantReturnMethod in covariantReturnMethods) - { - var overriddenMethod = otherMethods.FirstOrDefault(m => m.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); - if (overriddenMethod is null) - continue; + /// + /// Gets the inheritance depth of the specified type. + /// + /// The type whose inheritance depth is calculated. + /// + /// The inheritance depth of . + /// Returns 0 for , 1 for a class that directly inherits from , + /// 2 for its derived class, and so on. + /// + /// + /// Thrown when is . + /// + public static int GetInheritanceDepth(this Type? type) + { + if (type is null) + return 0; - var interfaceDeclarations = covariantReturnMethod.GetInterfaceDeclarations().ToHashSet(); - result.Add(new CovariantReturnMethodInfo(covariantReturnMethod, overriddenMethod, interfaceDeclarations)); - } + var depth = 0; + var current = type; - return result; + while (current.BaseType is not null) + { + depth++; + current = current.BaseType; } - /// - /// Gets the inheritance depth of the specified type. - /// - /// The type whose inheritance depth is calculated. - /// - /// The inheritance depth of . - /// Returns 0 for , 1 for a class that directly inherits from , - /// 2 for its derived class, and so on. - /// - /// - /// Thrown when is . - /// - public static int GetInheritanceDepth(this Type? type) + return depth - 1; // 去掉 object 自己那一层 + } + + /// + /// Determines whether the specified method is overridden by a covariant return method. + /// + /// + /// Assumes it is already a covariant return method. + /// + public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, MethodInfo covariantReturnMethod) + { + if (covariantReturnMethod.Name != method.Name) + return false; + + if (method.IsConstructedGenericMethod()) { - if (type is null) - return 0; + if (covariantReturnMethod.IsConstructedGenericMethod() == false) + return false; - var depth = 0; - var current = type; + method = method.GetGenericMethodDefinition(); + covariantReturnMethod = covariantReturnMethod.GetGenericMethodDefinition(); + } - while (current.BaseType != null) - { - depth++; - current = current.BaseType; - } + // return types should not be the same. + if (covariantReturnMethod.ReturnType == method.ReturnType) + return false; + + if (method.ReturnType.IsCovariantReturnAssignableFrom(covariantReturnMethod.ReturnType) == false) + return false; + + if (method.DeclaringType is not { } mdt + || covariantReturnMethod.DeclaringType is not { } crmdt + || mdt.IsAssignableFrom(crmdt) == false) + return false; + + var params1 = covariantReturnMethod.GetParameters(); + var params2 = method.GetParameters(); - return depth - 1; // 去掉 object 自己那一层 + if (params1.Length != params2.Length) + return false; + + foreach (var (p1, p2) in params1.Zip(params2)) + { + if (p1.ParameterType.IsCovariantReturnEquivalentTo(p2.ParameterType) == false) + return false; } - /// - /// Determines whether the specified method is overridden by a covariant return method. - /// - /// - /// Assumes it is already a covariant return method. - /// - public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, MethodInfo covariantReturnMethod) + var isGeneric = covariantReturnMethod.IsGenericMethod; + if (isGeneric != method.IsGenericMethod) + return false; + + if (isGeneric) { - if (covariantReturnMethod.Name != method.Name) + var args1 = covariantReturnMethod.GetGenericArguments(); + var args2 = method.GetGenericArguments(); + if (args1.Length != args2.Length) return false; - if (method.IsConstructedGenericMethod()) + foreach (var (a1, a2) in args1.Zip(args2)) { - if (covariantReturnMethod.IsConstructedGenericMethod() == false) + if (a1.GenericParameterPosition != a2.GenericParameterPosition) return false; - - method = method.GetGenericMethodDefinition(); - covariantReturnMethod = covariantReturnMethod.GetGenericMethodDefinition(); } + } - // return types should not be the same. - if (covariantReturnMethod.ReturnType == method.ReturnType) - return false; + return true; + } - if (method.ReturnType.IsAssignableFrom(covariantReturnMethod.ReturnType) == false) + private static bool AreEquivalentGenericTypes(Type type, Type other, Func argComparer) + { + if (type.IsArray && other.IsArray) + { + // ReSharper disable once TailRecursiveCall + return argComparer(type.GetElementType()!, other.GetElementType()!); + } + + if (type.IsGenericType == false || other.IsGenericType == false) + return false; + + if (type.IsConstructedGenericType) + { + if (other.IsConstructedGenericType == false) return false; - var params1 = covariantReturnMethod.GetParameters(); - var params2 = method.GetParameters(); + type = type.GetGenericTypeDefinition(); + other = other.GetGenericTypeDefinition(); + } + + if (argComparer(type, other) == false) + return false; + + var args1 = type.GetGenericArguments(); + var args2 = other.GetGenericArguments(); - if (params1.Length != params2.Length) + if (args1.Length != args2.Length) + return false; + + foreach (var (a1, a2) in args1.Zip(args2)) + { + if (argComparer(a1, a2) == false) return false; + } - foreach (var (p1, p2) in params1.Zip(params2)) - { - var t1 = p1.ParameterType; - var t2 = p2.ParameterType; + return true; + } - if (t1 == t2) - continue; - if (t1.IsGenericParameter == false || t2.IsGenericParameter == false) - return false; + // for covariant return types, the generic parameter position must be the same. + // the declaring method/type is different, we only check the null state here. + private static bool AreEquivalentGenericParameters(Type type, Type other) + { + if (type.IsGenericParameter == false || other.IsGenericParameter == false) + return false; - var m1 = t1.DeclaringMethod; - var m2 = t2.DeclaringMethod; + if (type.GenericParameterPosition != other.GenericParameterPosition) + return false; - if (m1 is null && m2 is not null - || m1 is not null && m2 is null) - return false; + if (type.DeclaringMethod.IsSameNullState(other.DeclaringMethod) == false) + return false; - if (t1.DeclaringMethod != t2.DeclaringMethod) - return false; + if (type.DeclaringType.IsSameNullState(other.DeclaringType) == false) + return false; - if (t1.DeclaringType != t2.DeclaringType) - return false; + return true; + } - if (t1.GenericParameterPosition != t2.GenericParameterPosition) - return false; - } + public static bool IsAssignableFromGenericTypeDefinition(this Type type, Type other) + { + if (type.IsGenericTypeDefinition == false) + return false; - var isGeneric = covariantReturnMethod.IsGenericMethod; - if (isGeneric != method.IsGenericMethod) + var p = other; + while (p is not null) + { + if (p.IsGenericTypeDefinition == false) return false; - if (isGeneric) + foreach (var it in p.GetInterfaces()) { - var args1 = covariantReturnMethod.GetGenericArguments(); - var args2 = method.GetGenericArguments(); - if (args1.Length != args2.Length) - return false; - - foreach (var (a1, a2) in args1.Zip(args2)) - { - if (a1.GenericParameterPosition != a2.GenericParameterPosition) - return false; - } + if (it.IsGenericType && it.GetGenericTypeDefinition() == type) + return true; } - return true; + p = p.BaseType; } + + return false; } -} + + public static bool IsCovariantReturnAssignableFrom(this Type type, Type other) + { + return type.IsAssignableFrom(other) + || type.IsAssignableFromGenericTypeDefinition(other) + || AreEquivalentGenericParameters(type, other) + || AreEquivalentGenericTypes(type, other, IsCovariantReturnAssignableFrom); + } + + public static bool IsCovariantReturnEquivalentTo(this Type type, Type other) + { + return type == other + || AreEquivalentGenericParameters(type, other) + || AreEquivalentGenericTypes(type, other, IsCovariantReturnEquivalentTo); + } +} \ No newline at end of file diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs index c7879fcc..b3aeff9a 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Linq; using System; using System.Reflection; @@ -95,13 +96,13 @@ public void ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverride() } [Fact] - public void ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverrideOfCovariantLeaf() + public void ShouldReturnTrue_WhenFinalOverrideInCovariantReturnChainOverridesBaseCovariantMethod() { var method = GetMethod(nameof(LeafCovariantReturnService.Method), typeof(BaseResult)); var ordinaryOverrideMethod = GetMethod(nameof(OrdinaryOverrideLeafService.Method), typeof(LeafResult)); Assert.True(ordinaryOverrideMethod.GetBaseDefinition() == GetMethod(nameof(LeafCovariantReturnService.Method), typeof(LeafResult)).GetBaseDefinition()); - Assert.False(method.IsOverriddenByCovariantReturnMethod(ordinaryOverrideMethod)); + Assert.True(method.IsOverriddenByCovariantReturnMethod(ordinaryOverrideMethod)); } [Fact] @@ -270,9 +271,8 @@ public void ShouldReturnFalse_WhenGenericParameterPositionsDiffer() public void ShouldReturnTrue_WhenCovariantReturnUsesConstrainedGenericParameter() { var method = GetMethod(nameof(ConstrainedGenericReturnBaseService.Create), typeof(BaseResult), parameterCount: 1); - var covariantReturnMethod = GetMethod( - nameof(ConstrainedGenericReturnLeafService.Create), - method => method.ReturnType.IsGenericParameter && method.GetParameters().Length == 1); + var covariantReturnMethod = GetMethod(nameof(ConstrainedGenericReturnLeafService.Create), + m => m.ReturnType.IsGenericParameter && m.GetParameters().Length == 1); Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -321,10 +321,16 @@ private static MethodInfo GetPropertyGetter(string name, Type propertyType) .GetMethod!; } - [Fact] - public void Print() + [Theory] + [InlineData(typeof(LeafCovariantReturnService))] + [InlineData(typeof(DerivedLeafCovariantReturnService))] + [InlineData(typeof(OrdinaryOverrideLeafService))] + [InlineData(typeof(DerivedOrdinaryOverrideLeafService))] + public void GetMethods_Print(Type type) { - var methods = typeof(DerivedOrdinaryOverrideLeafService).GetMethods(BindingFlags.Public | BindingFlags.Instance); + output.WriteLine($"{type.Name}'s Methods:"); + + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); foreach (var method in methods) { var dt = method.DeclaringType; @@ -335,8 +341,41 @@ public void Print() if (attributes.Any(m => m.AttributeType == typeof(CompilerGeneratedAttribute))) continue; + var isCrt = method.IsCovariantReturnMethod(); + var attributeNames = attributes.Select(a => a.AttributeType.Name); - output.WriteLine($"[{dt?.Name}.{method.Name}] Return Type: {method.ReturnType.Name}, Attributes: {string.Join(", ", attributeNames)}"); + output.WriteLine($"[{dt?.Name}.{method.Name}] Return Type: {method.ReturnType.Name}, Covariant Return: {isCrt}, Attributes: {string.Join(", ", attributeNames)}"); + } + } + + [Theory] + [InlineData(typeof(NestedClass<,>.InnerClass<,>))] + [InlineData(typeof(NestedClass.InnerClass))] + public void GenericParameter_Print(Type type) + { + output.WriteLine("GenericTypeParameters:"); + foreach (var param in type.GetTypeInfo().GenericTypeParameters) + { + output.WriteLine($"[{param.Name}]Declaring Type: {param.DeclaringType?.Name}, Declaring Method: {param.DeclaringMethod?.Name}"); + } + + var method = type.GetMethod("Method"); + Assert.NotNull(method); + + output.WriteLine("\nMethod Parameters:"); + foreach (var param in method.GetParameters()) + { + var pt = param.ParameterType; + var dm = pt.IsGenericParameter ? pt.DeclaringMethod : null; + output.WriteLine($"[{param.Name}]Type: {pt.Name}, Declaring Type: {pt.DeclaringType?.Name}, Declaring Method: {dm?.Name}"); + } + } + + public class NestedClass + { + public class InnerClass + { + public void Method(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { } } } } diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs index 901be1bc..72d72212 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs @@ -1,3 +1,4 @@ +#pragma warning disable CA1822 // Mark members as static using System.Collections.Generic; namespace AspectCore.Tests.Extensions.TypeExtensionsTests; From 30a96a0b863b00f4c96b0d6a82bc5ac32dff04a1 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 14:34:37 -0230 Subject: [PATCH 38/55] fix TypeExtensions --- .../Extensions/TypeExtensions.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 55dc41f4..48d57d9f 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -288,6 +288,15 @@ public static bool IsAssignableFromGenericTypeDefinition(this Type type, Type ot public static bool IsCovariantReturnAssignableFrom(this Type type, Type other) { + if (type.IsByRef) + { + if (other.IsByRef == false) + return false; + + type = type.GetElementType()!; + other = other.GetElementType()!; + } + return type.IsAssignableFrom(other) || type.IsAssignableFromGenericTypeDefinition(other) || AreEquivalentGenericParameters(type, other) @@ -296,6 +305,15 @@ public static bool IsCovariantReturnAssignableFrom(this Type type, Type other) public static bool IsCovariantReturnEquivalentTo(this Type type, Type other) { + if (type.IsByRef) + { + if (other.IsByRef == false) + return false; + + type = type.GetElementType()!; + other = other.GetElementType()!; + } + return type == other || AreEquivalentGenericParameters(type, other) || AreEquivalentGenericTypes(type, other, IsCovariantReturnEquivalentTo); From 34343d9a64adfcfc61f3d800650fee663f31dc42 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 15:09:09 -0230 Subject: [PATCH 39/55] fix TypeExtensions --- .../Extensions/TypeExtensions.cs | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 48d57d9f..6ffe33b5 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -167,9 +167,9 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M if (method.ReturnType.IsCovariantReturnAssignableFrom(covariantReturnMethod.ReturnType) == false) return false; - if (method.DeclaringType is not { } mdt - || covariantReturnMethod.DeclaringType is not { } crmdt - || mdt.IsAssignableFrom(crmdt) == false) + if (method.DeclaringType is not { } dt1 + || covariantReturnMethod.DeclaringType is not { } dt2 + || dt1.IsCovariantReturnAssignableFrom(dt2) == false) return false; var params1 = covariantReturnMethod.GetParameters(); @@ -205,12 +205,12 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M return true; } - private static bool AreEquivalentGenericTypes(Type type, Type other, Func argComparer) + private static bool AreEquivalentGenericTypes(Type type, Type other, Func argumentComparer, Func typeDefinitionComparer) { if (type.IsArray && other.IsArray) { // ReSharper disable once TailRecursiveCall - return argComparer(type.GetElementType()!, other.GetElementType()!); + return argumentComparer(type.GetElementType()!, other.GetElementType()!); } if (type.IsGenericType == false || other.IsGenericType == false) @@ -225,8 +225,14 @@ private static bool AreEquivalentGenericTypes(Type type, Type other, Func a.IsAssignableFromGenericTypeDefinition(b)); } public static bool IsCovariantReturnEquivalentTo(this Type type, Type other) { - if (type.IsByRef) - { - if (other.IsByRef == false) - return false; - - type = type.GetElementType()!; - other = other.GetElementType()!; - } + if (TryUnwrapByRef(ref type, ref other) == false) + return false; return type == other || AreEquivalentGenericParameters(type, other) - || AreEquivalentGenericTypes(type, other, IsCovariantReturnEquivalentTo); + || AreEquivalentGenericTypes(type, other, IsCovariantReturnEquivalentTo, (a, b) => a == b); } } \ No newline at end of file From f16aaf34d8a7d4c2f104a5f9baabe5c5933a6b76 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 21:23:43 -0230 Subject: [PATCH 40/55] fix test cases --- .../Extensions/TypeExtensions.cs | 7 ++- ...sOverriddenByCovariantReturnMethodTests.cs | 43 ++++++++++++++++++- .../TypeExtensionsTests/TestTypes.cs | 42 ++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 6ffe33b5..3eeea539 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -144,7 +144,7 @@ public static int GetInheritanceDepth(this Type? type) /// Determines whether the specified method is overridden by a covariant return method. /// /// - /// Assumes it is already a covariant return method. + /// /// public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, MethodInfo covariantReturnMethod) { @@ -197,7 +197,7 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M foreach (var (a1, a2) in args1.Zip(args2)) { - if (a1.GenericParameterPosition != a2.GenericParameterPosition) + if (a1.IsCovariantReturnEquivalentTo(a2) == false) return false; } } @@ -209,6 +209,9 @@ private static bool AreEquivalentGenericTypes(Type type, Type other, Func(nameof(LeafCovariantReturnService.Method), typeof(BaseResult)); var ordinaryOverrideMethod = GetMethod(nameof(OrdinaryOverrideLeafService.Method), typeof(LeafResult)); @@ -267,6 +267,15 @@ public void ShouldReturnFalse_WhenGenericParameterPositionsDiffer() Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } + [Fact] + public void ShouldReturnFalse_WhenTypeGenericParametersComeFromDifferentDeclaringTypes() + { + var method = GetMethod(typeof(TypeGenericParameterSourceBaseService<>), nameof(TypeGenericParameterSourceBaseService.Convert), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(typeof(TypeGenericParameterSourceLeafService<>), nameof(TypeGenericParameterSourceLeafService.Convert), typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + [Fact] public void ShouldReturnTrue_WhenCovariantReturnUsesConstrainedGenericParameter() { @@ -277,6 +286,36 @@ public void ShouldReturnTrue_WhenCovariantReturnUsesConstrainedGenericParameter( Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } + [Fact] + public void ShouldReturnFalse_WhenGenericArrayParameterRanksDiffer() + { + var method = GetMethod(nameof(ArrayRankBaseService.Convert), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(ArrayRankLeafService.Convert), typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnFalse_WhenInvariantGenericReturnArgumentsDiffer() + { + var method = GetMethod(nameof(InvariantGenericReturnBaseService.Create), + m => m.DeclaringType == typeof(InvariantGenericReturnBaseService) && m.ReturnType == typeof(System.Collections.Generic.List)); + var covariantReturnMethod = GetMethod(nameof(InvariantGenericReturnLeafService.Create), + m => m.DeclaringType == typeof(InvariantGenericReturnLeafService) && m.ReturnType == typeof(System.Collections.Generic.List)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnFalse_WhenOnlyCovariantReturnMethodIsConstructedGenericMethod() + { + var method = GetMethod(nameof(GenericMethodWithoutParameterBaseService.Create), typeof(BaseResult)); + var covariantReturnMethod = GetMethod(nameof(GenericMethodWithoutParameterLeafService.Create), typeof(LeafResult)) + .MakeGenericMethod(typeof(string)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + private static MethodInfo GetMethod(string name, Type returnType, int parameterCount = 0) { return GetMethod(typeof(T), name, returnType, parameterCount); diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs index 72d72212..27cc9b2d 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs @@ -1,4 +1,6 @@ #pragma warning disable CA1822 // Mark members as static +#pragma warning disable IDE0060 // Remove unused parameter +// ReSharper disable UnusedTypeParameter using System.Collections.Generic; namespace AspectCore.Tests.Extensions.TypeExtensionsTests; @@ -173,4 +175,44 @@ public class ConstrainedGenericReturnLeafService : ConstrainedGenericReturnBaseS { public override TValue Create(TValue value) => value; } + + public class TypeGenericParameterSourceBaseService + { + public virtual BaseResult Convert(TBase value) => new(nameof(TypeGenericParameterSourceBaseService)); + } + + public class TypeGenericParameterSourceLeafService : TypeGenericParameterSourceBaseService + { + public LeafResult Convert(TLeaf value) => new(nameof(TypeGenericParameterSourceLeafService)); + } + + public class ArrayRankBaseService + { + public virtual BaseResult Convert(TValue[] value) => new(nameof(ArrayRankBaseService)); + } + + public class ArrayRankLeafService : ArrayRankBaseService + { + public LeafResult Convert(TValue[,] value) => new(nameof(ArrayRankLeafService)); + } + + public class InvariantGenericReturnBaseService + { + public virtual List Create() => []; + } + + public class InvariantGenericReturnLeafService : InvariantGenericReturnBaseService + { + public new List Create() => []; + } + + public class GenericMethodWithoutParameterBaseService + { + public virtual BaseResult Create() => new(nameof(GenericMethodWithoutParameterBaseService)); + } + + public class GenericMethodWithoutParameterLeafService : GenericMethodWithoutParameterBaseService + { + public override LeafResult Create() => new(nameof(GenericMethodWithoutParameterLeafService)); + } } From 5d95656432b1e8929248ec901a7e08056777ccec Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 22:55:13 -0230 Subject: [PATCH 41/55] fix TypeExtensions --- .../Extensions/TypeExtensions.cs | 72 ++++++++++--------- ...sOverriddenByCovariantReturnMethodTests.cs | 39 +++++++--- .../TypeExtensionsTests/TestTypes.cs | 20 ++++++ 3 files changed, 88 insertions(+), 43 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 3eeea539..4532588f 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -143,22 +143,16 @@ public static int GetInheritanceDepth(this Type? type) /// /// Determines whether the specified method is overridden by a covariant return method. /// - /// - /// + /// The method that potentially has a covariant return type override. + /// The method that potentially overrides with a covariant return type. /// public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, MethodInfo covariantReturnMethod) { - if (covariantReturnMethod.Name != method.Name) + if (covariantReturnMethod.IsInCovariantReturnChain() == false) return false; - if (method.IsConstructedGenericMethod()) - { - if (covariantReturnMethod.IsConstructedGenericMethod() == false) - return false; - - method = method.GetGenericMethodDefinition(); - covariantReturnMethod = covariantReturnMethod.GetGenericMethodDefinition(); - } + if (covariantReturnMethod.Name != method.Name) + return false; // return types should not be the same. if (covariantReturnMethod.ReturnType == method.ReturnType) @@ -188,6 +182,9 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M if (isGeneric != method.IsGenericMethod) return false; + if (method.IsGenericMethodDefinition != covariantReturnMethod.IsGenericMethodDefinition) + return false; + if (isGeneric) { var args1 = covariantReturnMethod.GetGenericArguments(); @@ -219,23 +216,11 @@ private static bool AreEquivalentGenericTypes(Type type, Type other, Func a.IsAssignableFromGenericTypeDefinition(b)); + || AreEquivalentGenericTypes(type, other, + (a, b) => a.IsGenericParameterCovariant() + ? a.IsCovariantReturnAssignableFrom(b) + : a.IsCovariantReturnEquivalentTo(b), + (a, b) => a.IsAssignableFromGenericTypeDefinition(b)); } public static bool IsCovariantReturnEquivalentTo(this Type type, Type other) diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs index 33d4a7b3..c292925d 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs @@ -1,6 +1,8 @@ -#nullable enable +#nullable enable +#pragma warning disable IDE0060 // Remove unused parameter using System.Linq; using System; +using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using AspectCore.Extensions; @@ -198,8 +200,8 @@ public void ShouldReturnTrue_WhenGenericMethodParameterIsByRefMethodGenericParam [Fact] public void ShouldReturnTrue_WhenGenericMethodReturnTypeContainsMethodGenericParameter() { - var method = GetGenericReturnMethod(nameof(GenericMethodShapeBaseService.ReturnList), typeof(System.Collections.Generic.IEnumerable<>)); - var covariantReturnMethod = GetGenericReturnMethod(nameof(GenericMethodShapeLeafService.ReturnList), typeof(System.Collections.Generic.List<>)); + var method = GetGenericReturnMethod(nameof(GenericMethodShapeBaseService.ReturnList), typeof(IEnumerable<>)); + var covariantReturnMethod = GetGenericReturnMethod(nameof(GenericMethodShapeLeafService.ReturnList), typeof(List<>)); Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -225,8 +227,8 @@ public void ShouldReturnTrue_WhenParameterIsListOfTypeGenericParameter() [Fact] public void ShouldReturnTrue_WhenReturnTypeContainsTypeGenericParameter() { - var method = GetGenericReturnMethod(typeof(TypeGenericShapeBaseService<>), nameof(TypeGenericShapeBaseService.ReturnList), typeof(System.Collections.Generic.IEnumerable<>)); - var covariantReturnMethod = GetGenericReturnMethod(typeof(TypeGenericShapeLeafService<>), nameof(TypeGenericShapeLeafService.ReturnList), typeof(System.Collections.Generic.List<>)); + var method = GetGenericReturnMethod(typeof(TypeGenericShapeBaseService<>), nameof(TypeGenericShapeBaseService.ReturnList), typeof(IEnumerable<>)); + var covariantReturnMethod = GetGenericReturnMethod(typeof(TypeGenericShapeLeafService<>), nameof(TypeGenericShapeLeafService.ReturnList), typeof(List<>)); Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -295,13 +297,31 @@ public void ShouldReturnFalse_WhenGenericArrayParameterRanksDiffer() Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } + [Fact] + public void ShouldReturnFalse_WhenGenericJaggedArrayElementRanksDiffer() + { + var method = GetMethod(nameof(JaggedArrayRankBaseService.Convert), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(JaggedArrayRankLeafService.Convert), typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnFalse_WhenGenericArrayNestingDepthsDiffer() + { + var method = GetMethod(nameof(ArrayNestingBaseService.Convert), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(ArrayNestingLeafService.Convert), typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + [Fact] public void ShouldReturnFalse_WhenInvariantGenericReturnArgumentsDiffer() { var method = GetMethod(nameof(InvariantGenericReturnBaseService.Create), - m => m.DeclaringType == typeof(InvariantGenericReturnBaseService) && m.ReturnType == typeof(System.Collections.Generic.List)); + m => m.DeclaringType == typeof(InvariantGenericReturnBaseService) && m.ReturnType == typeof(List)); var covariantReturnMethod = GetMethod(nameof(InvariantGenericReturnLeafService.Create), - m => m.DeclaringType == typeof(InvariantGenericReturnLeafService) && m.ReturnType == typeof(System.Collections.Generic.List)); + m => m.DeclaringType == typeof(InvariantGenericReturnLeafService) && m.ReturnType == typeof(List)); Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } @@ -361,10 +381,7 @@ private static MethodInfo GetPropertyGetter(string name, Type propertyType) } [Theory] - [InlineData(typeof(LeafCovariantReturnService))] - [InlineData(typeof(DerivedLeafCovariantReturnService))] - [InlineData(typeof(OrdinaryOverrideLeafService))] - [InlineData(typeof(DerivedOrdinaryOverrideLeafService))] + [InlineData(typeof(MixedGenericShapeLeafService<>))] public void GetMethods_Print(Type type) { output.WriteLine($"{type.Name}'s Methods:"); diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs index 27cc9b2d..f4f7089d 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs @@ -196,6 +196,26 @@ public class ArrayRankLeafService : ArrayRankBaseService public LeafResult Convert(TValue[,] value) => new(nameof(ArrayRankLeafService)); } + public class JaggedArrayRankBaseService + { + public virtual BaseResult Convert(TValue[][] value) => new(nameof(JaggedArrayRankBaseService)); + } + + public class JaggedArrayRankLeafService : JaggedArrayRankBaseService + { + public LeafResult Convert(TValue[][,] value) => new(nameof(JaggedArrayRankLeafService)); + } + + public class ArrayNestingBaseService + { + public virtual BaseResult Convert(TValue[] value) => new(nameof(ArrayNestingBaseService)); + } + + public class ArrayNestingLeafService : ArrayNestingBaseService + { + public LeafResult Convert(TValue[][] value) => new(nameof(ArrayNestingLeafService)); + } + public class InvariantGenericReturnBaseService { public virtual List Create() => []; From e86e62e028f065d3e8b84521ccc914b55b400466 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 23:23:23 -0230 Subject: [PATCH 42/55] add test case --- ...sOverriddenByCovariantReturnMethodTests.cs | 9 ++++ .../TypeExtensionsTests/TestTypes.cs | 45 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs index c292925d..f4e859d0 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs @@ -278,6 +278,15 @@ public void ShouldReturnFalse_WhenTypeGenericParametersComeFromDifferentDeclarin Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); } + [Fact] + public void ShouldReturnFalse_WhenTypeGenericParametersComeFromDifferentDeclaringTypes_Emit() + { + var method = GetMethod(typeof(TypeGenericParameterSourceBaseService<>), nameof(TypeGenericParameterSourceBaseService.Convert), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(DynamicTypeGenericParameterSourceLeafService, "Convert", typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + [Fact] public void ShouldReturnTrue_WhenCovariantReturnUsesConstrainedGenericParameter() { diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs index f4f7089d..9ba1f067 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs @@ -1,7 +1,11 @@ #pragma warning disable CA1822 // Mark members as static #pragma warning disable IDE0060 // Remove unused parameter // ReSharper disable UnusedTypeParameter +using System; using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using Xunit; namespace AspectCore.Tests.Extensions.TypeExtensionsTests; @@ -235,4 +239,45 @@ public class GenericMethodWithoutParameterLeafService : GenericMethodWithoutPara { public override LeafResult Create() => new(nameof(GenericMethodWithoutParameterLeafService)); } + + private static Type CreateTypeGenericParameterSourceLeafService() + { + var assemblyName = new AssemblyName("DynamicTestTypes"); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicTestTypes"); + + var typeBuilder = moduleBuilder.DefineType( + "TypeGenericParameterSourceLeafService`1", + TypeAttributes.Public | TypeAttributes.Class, + typeof(TypeGenericParameterSourceBaseService<>).MakeGenericType(typeof(BaseResult))); + + var genericParameters = typeBuilder.DefineGenericParameters("TLeaf"); + var tLeaf = genericParameters[0]; + + var methodBuilder = typeBuilder.DefineMethod( + "Convert", + MethodAttributes.Public + | MethodAttributes.Virtual + | MethodAttributes.NewSlot + | MethodAttributes.HideBySig, + typeof(LeafResult), + [tLeaf]); + + var preserve = AspectCore.Extensions.TypeExtensions.PreserveBaseOverridesAttribute; + Assert.NotNull(preserve); + + var ctor = preserve.GetConstructor(Type.EmptyTypes); + Assert.NotNull(ctor); + + methodBuilder.SetCustomAttribute(new CustomAttributeBuilder(ctor, [])); + + var il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldstr, "TypeGenericParameterSourceLeafService"); + il.Emit(OpCodes.Newobj, typeof(LeafResult).GetConstructor([typeof(string)])!); + il.Emit(OpCodes.Ret); + + return typeBuilder.CreateTypeInfo().AsType(); + } + + public static readonly Type DynamicTypeGenericParameterSourceLeafService = CreateTypeGenericParameterSourceLeafService(); } From eed0506d9f5c52ed93c750862ba2442b69831844 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Tue, 7 Jul 2026 23:43:16 -0230 Subject: [PATCH 43/55] refine --- src/AspectCore.Core/Extensions/TypeExtensions.cs | 11 ++++++++--- .../IsOverriddenByCovariantReturnMethodTests.cs | 12 +++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 4532588f..1808c8c3 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -243,18 +243,20 @@ private static bool AreEquivalentGenericTypes(Type type, Type other, Func + /// Determines whether the specified generic parameter type is covariant. + /// + /// The type to check. + /// if the specified type is covariant; otherwise, . public static bool IsGenericParameterCovariant(this Type type) { if (type.IsGenericParameter == false) return false; var variance = type.GenericParameterAttributes & GenericParameterAttributes.VarianceMask; - - // Check against the Covariant enum value return variance == GenericParameterAttributes.Covariant; } - // for covariant return types, the generic parameter position must be the same. // the declaring method/type is different, we only check the null state here. private static bool AreEquivalentGenericParameters(Type type, Type other) @@ -271,6 +273,9 @@ private static bool AreEquivalentGenericParameters(Type type, Type other) if (type.DeclaringType.IsSameNullState(other.DeclaringType) == false) return false; + if (type.DeclaringType.IsSameNullState(other.DeclaringType) == false) + return false; + return true; } diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs index f4e859d0..bc0d4448 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs @@ -414,9 +414,10 @@ public void GetMethods_Print(Type type) } [Theory] - [InlineData(typeof(NestedClass<,>.InnerClass<,>))] - [InlineData(typeof(NestedClass.InnerClass))] - public void GenericParameter_Print(Type type) + [InlineData(typeof(NestedClass<,>.InnerClass<,>), "Method")] + [InlineData(typeof(NestedClass.InnerClass), "Method")] + [InlineData(typeof(MixedGenericShapeLeafService<>), "TypeAndMethod")] + public void GenericParameter_Print(Type type, string methodName) { output.WriteLine("GenericTypeParameters:"); foreach (var param in type.GetTypeInfo().GenericTypeParameters) @@ -424,10 +425,11 @@ public void GenericParameter_Print(Type type) output.WriteLine($"[{param.Name}]Declaring Type: {param.DeclaringType?.Name}, Declaring Method: {param.DeclaringMethod?.Name}"); } - var method = type.GetMethod("Method"); + var method = type.GetMethod(methodName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); Assert.NotNull(method); - output.WriteLine("\nMethod Parameters:"); + output.WriteLine(""); + output.WriteLine("Method Parameters:"); foreach (var param in method.GetParameters()) { var pt = param.ParameterType; From 5bb3323e57208ff5cb7fa6ae0d3c01cb198bf2a0 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 05:19:27 -0230 Subject: [PATCH 44/55] fix TypeExtensions --- .../Extensions/TypeExtensions.cs | 151 +++++++++++++++--- ...sOverriddenByCovariantReturnMethodTests.cs | 6 +- .../TypeExtensionsTests/TestTypes.cs | 2 +- 3 files changed, 134 insertions(+), 25 deletions(-) diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 1808c8c3..0f3a79d1 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -1,9 +1,8 @@ -#nullable enable +#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using AspectCore.Core.Extensions; // ReSharper disable once CheckNamespace namespace AspectCore.Extensions; @@ -154,16 +153,19 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M if (covariantReturnMethod.Name != method.Name) return false; - // return types should not be the same. - if (covariantReturnMethod.ReturnType == method.ReturnType) + if (method.DeclaringType is not { } dt1 + || covariantReturnMethod.DeclaringType is not { } dt2 + || dt1.IsCovariantReturnAssignableFrom(dt2) == false) return false; - if (method.ReturnType.IsCovariantReturnAssignableFrom(covariantReturnMethod.ReturnType) == false) + var genericParameterMap = CreateGenericParameterMap(method, covariantReturnMethod); + var methodReturnType = method.ReturnType.SubstituteGenericParameters(genericParameterMap); + + // return types should not be the same. + if (covariantReturnMethod.ReturnType == methodReturnType) return false; - if (method.DeclaringType is not { } dt1 - || covariantReturnMethod.DeclaringType is not { } dt2 - || dt1.IsCovariantReturnAssignableFrom(dt2) == false) + if (methodReturnType.IsCovariantReturnAssignableFrom(covariantReturnMethod.ReturnType) == false) return false; var params1 = covariantReturnMethod.GetParameters(); @@ -174,7 +176,8 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M foreach (var (p1, p2) in params1.Zip(params2)) { - if (p1.ParameterType.IsCovariantReturnEquivalentTo(p2.ParameterType) == false) + var parameterType = p2.ParameterType.SubstituteGenericParameters(genericParameterMap); + if (p1.ParameterType.IsCovariantReturnEquivalentTo(parameterType) == false) return false; } @@ -194,7 +197,7 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M foreach (var (a1, a2) in args1.Zip(args2)) { - if (a1.IsCovariantReturnEquivalentTo(a2) == false) + if (a1.IsCovariantReturnEquivalentTo(a2.SubstituteGenericParameters(genericParameterMap)) == false) return false; } } @@ -202,6 +205,120 @@ public static bool IsOverriddenByCovariantReturnMethod(this MethodInfo method, M return true; } + private static IReadOnlyDictionary CreateGenericParameterMap(MethodInfo method, MethodInfo covariantReturnMethod) + { + var result = new Dictionary(); + + if (method.DeclaringType is { } declaringType && covariantReturnMethod.DeclaringType is { } covariantDeclaringType) + AddTypeGenericParameterMap(declaringType, covariantDeclaringType, result); + + if (method.IsGenericMethod && covariantReturnMethod.IsGenericMethod) + { + var args1 = method.GetGenericArguments(); + var args2 = covariantReturnMethod.GetGenericArguments(); + foreach (var (a1, a2) in args1.Zip(args2)) + { + if (a1.IsGenericParameter) + result[a1] = a2; + } + } + + return result; + } + + private static void AddTypeGenericParameterMap(Type declaringType, Type covariantDeclaringType, Dictionary result) + { + if (declaringType.IsGenericType == false) + return; + + var projectedDeclaringType = FindMatchingBaseType(covariantDeclaringType, declaringType); + if (projectedDeclaringType is null || projectedDeclaringType.IsGenericType == false) + return; + + var genericParameters = declaringType.GetGenericTypeDefinition().GetGenericArguments(); + var genericArguments = projectedDeclaringType.GetGenericArguments(); + if (genericParameters.Length != genericArguments.Length) + return; + + foreach (var (parameter, argument) in genericParameters.Zip(genericArguments)) + { + result[parameter] = argument; + } + } + + private static Type? FindMatchingBaseType(Type type, Type declaringType) + { + var declaringTypeDefinition = declaringType.IsGenericType + ? declaringType.GetGenericTypeDefinition() + : declaringType; + + foreach (var candidate in EnumerateBaseTypesAndInterfaces(type)) + { + if (candidate.IsGenericType) + { + if (candidate.GetGenericTypeDefinition() == declaringTypeDefinition) + return candidate; + } + else if (candidate == declaringTypeDefinition) + { + return candidate; + } + } + + return null; + } + + private static IEnumerable EnumerateBaseTypesAndInterfaces(Type type) + { + for (var current = type; current is not null; current = current.BaseType) + { + yield return current; + + foreach (var @interface in current.GetInterfaces()) + { + yield return @interface; + } + } + } + + private static Type SubstituteGenericParameters(this Type type, IReadOnlyDictionary genericParameterMap) + { + if (type.IsGenericParameter) + { + return genericParameterMap.GetValueOrDefault(type, type); + } + + if (type.HasElementType) + { + var elementType = type.GetElementType()!.SubstituteGenericParameters(genericParameterMap); + if (elementType == type.GetElementType()) + return type; + + if (type.IsArray) + return type.GetArrayRank() == 1 ? elementType.MakeArrayType() : elementType.MakeArrayType(type.GetArrayRank()); + + if (type.IsByRef) + return elementType.MakeByRefType(); + + if (type.IsPointer) + return elementType.MakePointerType(); + + return type; + } + + if (type.IsGenericType && type.IsGenericTypeDefinition == false) + { + var args = type.GetGenericArguments(); + var substitutedArgs = args.Select(a => a.SubstituteGenericParameters(genericParameterMap)).ToArray(); + if (args.SequenceEqual(substitutedArgs)) + return type; + + return type.GetGenericTypeDefinition().MakeGenericType(substitutedArgs); + } + + return type; + } + private static bool AreEquivalentGenericTypes(Type type, Type other, Func argumentComparer, Func typeDefinitionComparer) { if (type.IsArray && other.IsArray) @@ -257,8 +374,6 @@ public static bool IsGenericParameterCovariant(this Type type) return variance == GenericParameterAttributes.Covariant; } - // for covariant return types, the generic parameter position must be the same. - // the declaring method/type is different, we only check the null state here. private static bool AreEquivalentGenericParameters(Type type, Type other) { if (type.IsGenericParameter == false || other.IsGenericParameter == false) @@ -267,16 +382,10 @@ private static bool AreEquivalentGenericParameters(Type type, Type other) if (type.GenericParameterPosition != other.GenericParameterPosition) return false; - if (type.DeclaringMethod.IsSameNullState(other.DeclaringMethod) == false) - return false; - - if (type.DeclaringType.IsSameNullState(other.DeclaringType) == false) + if (type.DeclaringMethod != other.DeclaringMethod) return false; - if (type.DeclaringType.IsSameNullState(other.DeclaringType) == false) - return false; - - return true; + return type.DeclaringType == other.DeclaringType; } public static bool IsAssignableFromGenericTypeDefinition(this Type type, Type other) @@ -353,4 +462,4 @@ public static bool IsCovariantReturnEquivalentTo(this Type type, Type other) || AreEquivalentGenericParameters(type, other) || AreEquivalentGenericTypes(type, other, IsCovariantReturnEquivalentTo, (a, b) => a == b); } -} \ No newline at end of file +} diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs index bc0d4448..8b899ba8 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable #pragma warning disable IDE0060 // Remove unused parameter using System.Linq; using System; @@ -270,7 +270,7 @@ public void ShouldReturnFalse_WhenGenericParameterPositionsDiffer() } [Fact] - public void ShouldReturnFalse_WhenTypeGenericParametersComeFromDifferentDeclaringTypes() + public void ShouldReturnFalse_WhenBaseTypeGenericParameterIsSubstitutedByConcreteType() { var method = GetMethod(typeof(TypeGenericParameterSourceBaseService<>), nameof(TypeGenericParameterSourceBaseService.Convert), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(typeof(TypeGenericParameterSourceLeafService<>), nameof(TypeGenericParameterSourceLeafService.Convert), typeof(LeafResult), parameterCount: 1); @@ -279,7 +279,7 @@ public void ShouldReturnFalse_WhenTypeGenericParametersComeFromDifferentDeclarin } [Fact] - public void ShouldReturnFalse_WhenTypeGenericParametersComeFromDifferentDeclaringTypes_Emit() + public void ShouldReturnFalse_WhenEmittedCovariantReturnCandidateUsesUnrelatedTypeGenericParameter() { var method = GetMethod(typeof(TypeGenericParameterSourceBaseService<>), nameof(TypeGenericParameterSourceBaseService.Convert), typeof(BaseResult), parameterCount: 1); var covariantReturnMethod = GetMethod(DynamicTypeGenericParameterSourceLeafService, "Convert", typeof(LeafResult), parameterCount: 1); diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs index 9ba1f067..84d6100a 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs @@ -260,7 +260,7 @@ private static Type CreateTypeGenericParameterSourceLeafService() | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, - typeof(LeafResult), + typeof(object), [tLeaf]); var preserve = AspectCore.Extensions.TypeExtensions.PreserveBaseOverridesAttribute; From 23e2f4af2a6bf7f4fb46fa2ba56de703747cdc85 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 06:16:30 -0230 Subject: [PATCH 45/55] fix ClassProxyAstBuilder.cs --- .../Builders/ClassProxyAstBuilder.cs | 50 ++++++---------- .../Extensions/TypeExtensions.cs | 1 + .../DynamicProxy/CovariantReturnTypeTests.cs | 60 +++++++++---------- 3 files changed, 49 insertions(+), 62 deletions(-) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index 7f02f1ec..83223e6d 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs @@ -173,16 +173,14 @@ private void BuildClassMethods(List methods, List m.OverriddenMethod.IsSameBaseDefinition(method)) + .Where(m => method.IsOverriddenByCovariantReturnMethod(m.CovariantReturnMethod)) .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method .FirstOrDefault(); var overridden = covariantReturn.OverriddenMethod; if (overridden == null) return (null, false); - - // TODO: when method is also a covariant return method, it should be skipped as well. - + if (method.DeclaringType == method.ReflectedType) { // the 'method' is declared in the _serviceType, and it is overridden by a covariant-return method in the _implType. @@ -248,8 +246,11 @@ private void BuildClassProperties(List properties, List IsOverriddenByCovariantReturnProperty(property, m)) + .Where(m => getter.IsOverriddenByCovariantReturnMethod(m.CovariantReturnMethod)) .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method .FirstOrDefault(); @@ -257,33 +258,18 @@ private void BuildClassProperties(List properties, List /// Verifies that an object is exactly the given type (and not a derived type), and that it satisfies the given predicate. /// - private static void AssertTypeValue(object value, Func predicate) + private static void AssertTypeValue(object value, Action action) { var v = Assert.IsType(value); - Assert.True(predicate(v)); + action(v); } [Fact] public void CreateClassProxy_ForCovariantReturnType_ShouldUseStringReturnType() { var service = ProxyGenerator.CreateClassProxy(); - AssertTypeValue(service.Method(), v => v.Name == nameof(BaseCovariantReturnService)); - AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); - AssertTypeValue(service.Property, v => v.Name == nameof(BaseCovariantReturnService)); - AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(BaseCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(BaseCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } [Fact] public void CreateClassProxy_ForDerivedCovariantReturnType_ShouldUseOverriddenInterceptedMembers() { var service = ProxyGenerator.CreateClassProxy(); - AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); - AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); - AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); - AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(MidCovariantReturnService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(MidCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } [Fact] public void CreateClassProxy_ForBaseServiceAndCovariantImplementation_ShouldUseStringReturnType() { var service = ProxyGenerator.CreateClassProxy(); - AssertTypeValue(service.Property, v => v.Name == nameof(BaseCovariantReturnService)); - AssertTypeValue(service.Method(), v => v.Name == nameof(BaseCovariantReturnService)); - AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); - AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(BaseCovariantReturnService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(BaseCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } [Fact] public void CreateClassProxy_ForBaseServiceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { var service = ProxyGenerator.CreateClassProxy(); - AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); - AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); - AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); - AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(MidCovariantReturnService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(MidCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } [Fact] public void CreateClassProxy_ForCovariantServiceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { var service = ProxyGenerator.CreateClassProxy(); - AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); - AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); - AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); - AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(MidCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(MidCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } [Fact] public void CreateInterfaceProxy_ForBaseInterfaceAndCovariantImplementation_ShouldUseStringReturnType() { var service = ProxyGenerator.CreateInterfaceProxy(); - AssertTypeValue(service.Property, v => v.Name == nameof(BaseCovariantReturnService)); - AssertTypeValue(service.Method(), v => v.Name == nameof(BaseCovariantReturnService)); - AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); - AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(BaseCovariantReturnService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(BaseCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(BaseCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } [Fact] public void CreateInterfaceProxy_ForBaseInterfaceAndDerivedImplementation_ShouldUseDerivedInterceptedMembers() { var service = ProxyGenerator.CreateInterfaceProxy(); - AssertTypeValue(service.Property, v => v.Name == nameof(MidCovariantReturnService)); - AssertTypeValue(service.Method(), v => v.Name == nameof(MidCovariantReturnService)); - AssertTypeValue(service.InterceptedProperty, v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); - AssertTypeValue(service.InterceptedMethod(), v => v.Name == nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(MidCovariantReturnService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(MidCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } } From 0f2691a9ce90c811fa57e5d3a42d64136f5d7be5 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 07:50:37 -0230 Subject: [PATCH 46/55] fix test cases --- .../ProxyBuilder/Builders/ClassProxyAstBuilder.cs | 14 ++++++++++++-- src/AspectCore.Core/Extensions/TypeExtensions.cs | 2 ++ .../DynamicProxy/CovariantReturnTypeTests.cs | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index 83223e6d..a15532de 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs @@ -180,7 +180,7 @@ private void BuildClassMethods(List methods, List properties, List getter.IsOverriddenByCovariantReturnMethod(m.CovariantReturnMethod)) .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 8ad722d9..f5de3bd3 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -2,12 +2,14 @@ #pragma warning disable CA1859 // Use concrete types when possible for improved performance using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; // ReSharper disable once CheckNamespace namespace AspectCore.Extensions; +[DebuggerDisplay("({OverriddenMethod.ReturnType.Name} {OverriddenMethod.Name}) -> ({CovariantReturnMethod.ReturnType.Name} {CovariantReturnMethod.Name})")] internal readonly struct CovariantReturnMethodInfo { /// diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs index 850c2c38..7846016a 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs @@ -164,7 +164,7 @@ public class OrdinaryOverrideService : LeafCovariantReturnService public override LeafResult InterceptedMethod() => new(nameof(OrdinaryOverrideService)); } - // this class just inherits from OrdinaryOverrideService, and does not override any members. + //this class just inherits from OrdinaryOverrideService, and does not override any members. public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; } From eee02a851d7a50280bdafa2ec9b9c7e6eade9df9 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 08:14:11 -0230 Subject: [PATCH 47/55] refine code --- AspectCore-Framework.sln | 4 +- src/AspectCore.Core/AspectCore.Core.csproj | 1 + .../EnumerableServiceDefinition.cs | 25 +++ .../EnumerableServiceDefintion.cs | 25 --- .../ServiceCallSiteResolver.cs | 16 +- .../DependencyInjection/ServiceTable.cs | 4 +- .../Extensions/CollectionExtensions.cs | 22 +-- .../Extensions/EnumerableExtensions.cs | 12 +- .../Extensions/MethodInfoExtensions.cs | 143 +++++++++--------- .../Extensions/ObjectExtensions.cs | 2 +- .../Extensions/TypeExtensions.cs | 1 - 11 files changed, 127 insertions(+), 128 deletions(-) create mode 100644 src/AspectCore.Core/DependencyInjection/EnumerableServiceDefinition.cs delete mode 100644 src/AspectCore.Core/DependencyInjection/EnumerableServiceDefintion.cs diff --git a/AspectCore-Framework.sln b/AspectCore-Framework.sln index 59ea1f01..07362618 100644 --- a/AspectCore-Framework.sln +++ b/AspectCore-Framework.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.7.11925.98 stable +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35514.174 d17.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{73F38B6C-1A05-41C8-8029-D1F2F41D3279}" EndProject diff --git a/src/AspectCore.Core/AspectCore.Core.csproj b/src/AspectCore.Core/AspectCore.Core.csproj index f9489a55..bf3ad959 100644 --- a/src/AspectCore.Core/AspectCore.Core.csproj +++ b/src/AspectCore.Core/AspectCore.Core.csproj @@ -2,6 +2,7 @@ The implementation of the AspectCore framework. + AspectCore AspectCore.Core true AspectCore.Core diff --git a/src/AspectCore.Core/DependencyInjection/EnumerableServiceDefinition.cs b/src/AspectCore.Core/DependencyInjection/EnumerableServiceDefinition.cs new file mode 100644 index 00000000..4efdac88 --- /dev/null +++ b/src/AspectCore.Core/DependencyInjection/EnumerableServiceDefinition.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace AspectCore.DependencyInjection +{ + internal class EnumerableServiceDefinition : ServiceDefinition + { + public IEnumerable ServiceDefinitions { get; } + + public Type ElementType { get; } + + public EnumerableServiceDefinition(Type serviceType, Type elementType, IEnumerable serviceDefinitions) : base(serviceType, Lifetime.Transient) + { + ElementType = elementType; + ServiceDefinitions = serviceDefinitions; + } + } + + internal class ManyEnumerableServiceDefinition : EnumerableServiceDefinition + { + public ManyEnumerableServiceDefinition(Type serviceType, Type elementType, IEnumerable serviceDefinitions) : base(serviceType, elementType, serviceDefinitions) + { + } + } +} \ No newline at end of file diff --git a/src/AspectCore.Core/DependencyInjection/EnumerableServiceDefintion.cs b/src/AspectCore.Core/DependencyInjection/EnumerableServiceDefintion.cs deleted file mode 100644 index afa29754..00000000 --- a/src/AspectCore.Core/DependencyInjection/EnumerableServiceDefintion.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace AspectCore.DependencyInjection -{ - internal class EnumerableServiceDefintion : ServiceDefinition - { - public IEnumerable ServiceDefinitions { get; } - - public Type ElementType { get; } - - public EnumerableServiceDefintion(Type serviceType, Type elementType, IEnumerable serviceDefinitions) : base(serviceType, Lifetime.Transient) - { - ElementType = elementType; - ServiceDefinitions = serviceDefinitions; - } - } - - internal class ManyEnumerableServiceDefintion : EnumerableServiceDefintion - { - public ManyEnumerableServiceDefintion(Type serviceType, Type elementType, IEnumerable serviceDefinitions) : base(serviceType, elementType, serviceDefinitions) - { - } - } -} \ No newline at end of file diff --git a/src/AspectCore.Core/DependencyInjection/ServiceCallSiteResolver.cs b/src/AspectCore.Core/DependencyInjection/ServiceCallSiteResolver.cs index a2fab5a9..6afbceb5 100644 --- a/src/AspectCore.Core/DependencyInjection/ServiceCallSiteResolver.cs +++ b/src/AspectCore.Core/DependencyInjection/ServiceCallSiteResolver.cs @@ -56,19 +56,19 @@ private Func ResolveInternal(ServiceDefinition service) return delegateServiceDefinition.ImplementationDelegate; case TypeServiceDefinition typeServiceDefinition: return ResolveTypeService(typeServiceDefinition); - case ManyEnumerableServiceDefintion manyEnumerableServiceDefinition: + case ManyEnumerableServiceDefinition manyEnumerableServiceDefinition: return ResolveManyEnumerableService(manyEnumerableServiceDefinition); - case EnumerableServiceDefintion enumerableServiceDefinition: + case EnumerableServiceDefinition enumerableServiceDefinition: return ResolveEnumerableService(enumerableServiceDefinition); default: return resolver => null; } } - private Func ResolveManyEnumerableService(ManyEnumerableServiceDefintion manyEnumerableServiceDefintion) + private Func ResolveManyEnumerableService(ManyEnumerableServiceDefinition manyEnumerableServiceDefinition) { - var elementDefinitions = manyEnumerableServiceDefintion.ServiceDefinitions.ToArray(); - var elementType = manyEnumerableServiceDefintion.ElementType; + var elementDefinitions = manyEnumerableServiceDefinition.ServiceDefinitions.ToArray(); + var elementType = manyEnumerableServiceDefinition.ElementType; return resolver => { var length = elementDefinitions.Length; @@ -82,10 +82,10 @@ private Func ResolveManyEnumerableService(ManyEnumerabl }; } - private Func ResolveEnumerableService(EnumerableServiceDefintion enumerableServiceDefintion) + private Func ResolveEnumerableService(EnumerableServiceDefinition enumerableServiceDefinition) { - var elementDefinitions = enumerableServiceDefintion.ServiceDefinitions.ToArray(); - var elementType = enumerableServiceDefintion.ElementType; + var elementDefinitions = enumerableServiceDefinition.ServiceDefinitions.ToArray(); + var elementType = enumerableServiceDefinition.ElementType; return resolver => { var length = elementDefinitions.Length; diff --git a/src/AspectCore.Core/DependencyInjection/ServiceTable.cs b/src/AspectCore.Core/DependencyInjection/ServiceTable.cs index 15f4d70b..ecfa2573 100644 --- a/src/AspectCore.Core/DependencyInjection/ServiceTable.cs +++ b/src/AspectCore.Core/DependencyInjection/ServiceTable.cs @@ -116,7 +116,7 @@ private ServiceDefinition FindEnumerable(Type serviceType) } var elementType = serviceType.GetTypeInfo().GetGenericArguments()[0]; var elements = FindEnumerableElements(serviceType); - var enumerableServiceDefinition = new EnumerableServiceDefintion(serviceType, elementType, elements); + var enumerableServiceDefinition = new EnumerableServiceDefinition(serviceType, elementType, elements); _linkedServiceDefinitions[serviceType] = new LinkedList(new ServiceDefinition[] { enumerableServiceDefinition }); return enumerableServiceDefinition; } @@ -129,7 +129,7 @@ private ServiceDefinition FindManyEnumerable(Type serviceType) } var elementType = serviceType.GetTypeInfo().GetGenericArguments()[0]; var elements = FindEnumerableElements(serviceType); - var enumerableServiceDefinition = new ManyEnumerableServiceDefintion(serviceType, elementType, elements); + var enumerableServiceDefinition = new ManyEnumerableServiceDefinition(serviceType, elementType, elements); _linkedServiceDefinitions[serviceType] = new LinkedList(new ServiceDefinition[] { enumerableServiceDefinition }); return enumerableServiceDefinition; } diff --git a/src/AspectCore.Core/Extensions/CollectionExtensions.cs b/src/AspectCore.Core/Extensions/CollectionExtensions.cs index cd6d1249..dfc13e6c 100644 --- a/src/AspectCore.Core/Extensions/CollectionExtensions.cs +++ b/src/AspectCore.Core/Extensions/CollectionExtensions.cs @@ -1,15 +1,15 @@ // ReSharper disable once CheckNamespace -namespace System.Collections.Generic +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Collections.Generic; + +internal static class CollectionExtensions { - internal static class CollectionExtensions - { #if NETSTANDARD2_0 - public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue) - { - return dictionary.TryGetValue(key, out var obj) - ? obj - : defaultValue; - } -#endif + public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue) + { + return dictionary.TryGetValue(key, out var obj) + ? obj + : defaultValue; } -} +#endif +} \ No newline at end of file diff --git a/src/AspectCore.Core/Extensions/EnumerableExtensions.cs b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs index 0706d89f..0d60b6d8 100644 --- a/src/AspectCore.Core/Extensions/EnumerableExtensions.cs +++ b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs @@ -1,10 +1,13 @@ -using System.Collections.Generic; +#if NETSTANDARD2_0 || NETSTANDARD2_1 +using System.Collections.Generic; +#endif // ReSharper disable once CheckNamespace -namespace System.Linq +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Linq; + +internal static class EnumerableExtensions { - internal static class EnumerableExtensions - { #if NETSTANDARD2_0 || NETSTANDARD2_1 public static IEnumerable<(TFirst First, TSecond Second)> Zip(this IEnumerable first, IEnumerable second) { @@ -18,5 +21,4 @@ public static HashSet ToHashSet(this IEnumerable sour return new HashSet(source, comparer); } #endif - } } \ No newline at end of file diff --git a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs index 84dd1f05..6acde303 100644 --- a/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -3,92 +3,89 @@ using System.Reflection; using static AspectCore.Extensions.TypeExtensions; -// ReSharper disable once CheckNamespace -namespace AspectCore.Extensions +namespace AspectCore.Extensions; + +internal static class MethodInfoExtensions { - internal static class MethodInfoExtensions + public static IEnumerable GetInterfaceDeclarations(this MethodInfo method) { - public static IEnumerable GetInterfaceDeclarations(this MethodInfo method) - { - var typeInfo = method.ReflectedType?.GetTypeInfo(); - if (typeInfo is null) - yield break; + var typeInfo = method.ReflectedType?.GetTypeInfo(); + if (typeInfo is null) + yield break; - foreach (var implementedInterface in typeInfo.ImplementedInterfaces) + foreach (var implementedInterface in typeInfo.ImplementedInterfaces) + { + var map = typeInfo.GetInterfaceMap(implementedInterface); + foreach (var (interfaceMethod, targetMethod) in map.InterfaceMethods.Zip(map.TargetMethods)) { - var map = typeInfo.GetInterfaceMap(implementedInterface); - foreach (var (interfaceMethod, targetMethod) in map.InterfaceMethods.Zip(map.TargetMethods)) - { - if (targetMethod == method) - yield return interfaceMethod; - } + if (targetMethod == method) + yield return interfaceMethod; } } + } - /// - /// Determines whether the method itself is a covariant-return override method. - /// - /// - /// The method to inspect. - /// - /// - /// if the method is marked with - /// PreserveBaseOverridesAttribute; otherwise, - /// . - /// - public static bool IsCovariantReturnMethod(this MethodInfo method) - { - return PreserveBaseOverridesAttribute != null - && method.IsDefined(PreserveBaseOverridesAttribute); - } + /// + /// Determines whether the method itself is a covariant-return override method. + /// + /// + /// The method to inspect. + /// + /// + /// if the method is marked with + /// PreserveBaseOverridesAttribute; otherwise, + /// . + /// + public static bool IsCovariantReturnMethod(this MethodInfo method) + { + return PreserveBaseOverridesAttribute != null + && method.IsDefined(PreserveBaseOverridesAttribute); + } - /// - /// Determines whether the method participates in a covariant-return override chain. - /// - /// - /// The method to inspect. - /// - /// - /// if the method itself, or its base definition, - /// is a covariant-return override method; otherwise, - /// . - /// - public static bool IsInCovariantReturnChain(this MethodInfo method) - { - if (method.IsCovariantReturnMethod()) - return true; + /// + /// Determines whether the method participates in a covariant-return override chain. + /// + /// + /// The method to inspect. + /// + /// + /// if the method itself, or its base definition, + /// is a covariant-return override method; otherwise, + /// . + /// + public static bool IsInCovariantReturnChain(this MethodInfo method) + { + if (method.IsCovariantReturnMethod()) + return true; - return method.GetBaseDefinition() - .IsCovariantReturnMethod(); - } + return method.GetBaseDefinition() + .IsCovariantReturnMethod(); + } - /// - /// Determines whether two methods belong to the same virtual override chain - /// by comparing their base definitions. - /// - /// - /// The first method to compare. - /// - /// - /// The second method to compare. - /// - /// - /// if both methods have the same base definition; - /// otherwise, . - /// - public static bool IsSameBaseDefinition(this MethodInfo method, MethodInfo other) - { - return method.GetBaseDefinition() == other.GetBaseDefinition(); - } + /// + /// Determines whether two methods belong to the same virtual override chain + /// by comparing their base definitions. + /// + /// + /// The first method to compare. + /// + /// + /// The second method to compare. + /// + /// + /// if both methods have the same base definition; + /// otherwise, . + /// + public static bool IsSameBaseDefinition(this MethodInfo method, MethodInfo other) + { + return method.GetBaseDefinition() == other.GetBaseDefinition(); + } - public static bool IsConstructedGenericMethod(this MethodInfo method) - { + public static bool IsConstructedGenericMethod(this MethodInfo method) + { #if NETSTANDARD2_0 - return method.IsGenericMethod && !method.IsGenericMethodDefinition; + return method.IsGenericMethod && !method.IsGenericMethodDefinition; #else return method.IsConstructedGenericMethod; #endif - } } -} - +} \ No newline at end of file diff --git a/src/AspectCore.Core/Extensions/ObjectExtensions.cs b/src/AspectCore.Core/Extensions/ObjectExtensions.cs index 59762d5b..2d46e4e9 100644 --- a/src/AspectCore.Core/Extensions/ObjectExtensions.cs +++ b/src/AspectCore.Core/Extensions/ObjectExtensions.cs @@ -1,5 +1,5 @@ #nullable enable -namespace AspectCore.Core.Extensions; +namespace AspectCore.Extensions; internal static class ObjectExtensions { diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index f5de3bd3..476410d9 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Reflection; -// ReSharper disable once CheckNamespace namespace AspectCore.Extensions; [DebuggerDisplay("({OverriddenMethod.ReturnType.Name} {OverriddenMethod.Name}) -> ({CovariantReturnMethod.ReturnType.Name} {CovariantReturnMethod.Name})")] From c81a3e610a741ed368bbdf6bb1274cfad935888e Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 08:15:09 -0230 Subject: [PATCH 48/55] fix test cases --- .../Extensions/TypeExtensionsTests/TestTypes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs index 84d6100a..9ba1f067 100644 --- a/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs @@ -260,7 +260,7 @@ private static Type CreateTypeGenericParameterSourceLeafService() | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, - typeof(object), + typeof(LeafResult), [tLeaf]); var preserve = AspectCore.Extensions.TypeExtensions.PreserveBaseOverridesAttribute; From 30b5db85b3ee6040dc7fcc048ea6dd7ec95dc004 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 08:23:15 -0230 Subject: [PATCH 49/55] refine files --- .../CovariantReturnTypeTests.Generic.cs | 5 ++ .../CovariantReturnTypeTests.TestTypes.cs | 84 +++++++++++++++++++ .../DynamicProxy/CovariantReturnTypeTests.cs | 81 ------------------ 3 files changed, 89 insertions(+), 81 deletions(-) create mode 100644 tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs create mode 100644 tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.TestTypes.cs diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs new file mode 100644 index 00000000..3ab5288b --- /dev/null +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs @@ -0,0 +1,5 @@ +namespace AspectCore.Tests.DynamicProxy; + +partial class CovariantReturnTypeTests +{ +} diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.TestTypes.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.TestTypes.cs new file mode 100644 index 00000000..ad300908 --- /dev/null +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.TestTypes.cs @@ -0,0 +1,84 @@ +using AspectCore.DynamicProxy; +using System.Threading.Tasks; + +namespace AspectCore.Tests.DynamicProxy; + +partial class CovariantReturnTypeTests +{ + public class ReturnTypeInterceptor : AbstractInterceptorAttribute + { + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + await context.Invoke(next); + + if (context.ReturnValue is BaseResult returnValue) + { + returnValue.Name += nameof(ReturnTypeInterceptor); + } + } + } + + public interface ICommonService + { + object Property { get; } + object Method(); + + object InterceptedProperty { [ReturnTypeInterceptor] get; } + [ReturnTypeInterceptor] + object InterceptedMethod(); + } + + public class CommonService : ICommonService + { + public virtual object Property { get; } = nameof(Property); + public virtual object Method() => nameof(Method); + + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); + [ReturnTypeInterceptor] + public virtual object InterceptedMethod() => nameof(InterceptedMethod); + } + + public class BaseCovariantReturnService : CommonService + { + public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); + public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); + + public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); + [ReturnTypeInterceptor] + public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); + } + + public class MidCovariantReturnService : BaseCovariantReturnService + { + public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); + public override MidResult Method() => new(nameof(MidCovariantReturnService)); + + public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); + [ReturnTypeInterceptor] + public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); + } + + public class LeafCovariantReturnService : MidCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); + public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); + } + + // this class just does ordinary overriding. + public class OrdinaryOverrideService : LeafCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(OrdinaryOverrideService)); + public override LeafResult Method() => new(nameof(OrdinaryOverrideService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(OrdinaryOverrideService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(OrdinaryOverrideService)); + } + + //this class just inherits from OrdinaryOverrideService, and does not override any members. + public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; +} \ No newline at end of file diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs index 7846016a..d12222e2 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs @@ -87,84 +87,3 @@ public void CreateInterfaceProxy_ForBaseInterfaceAndDerivedImplementation_Should } } -// define a partial class to hold the test types. -partial class CovariantReturnTypeTests -{ - public class ReturnTypeInterceptor : AbstractInterceptorAttribute - { - public override async Task Invoke(AspectContext context, AspectDelegate next) - { - await context.Invoke(next); - - if (context.ReturnValue is BaseResult returnValue) - { - returnValue.Name += nameof(ReturnTypeInterceptor); - } - } - } - - public interface ICommonService - { - object Property { get; } - object Method(); - - object InterceptedProperty { [ReturnTypeInterceptor] get; } - [ReturnTypeInterceptor] - object InterceptedMethod(); - } - - public class CommonService : ICommonService - { - public virtual object Property { get; } = nameof(Property); - public virtual object Method() => nameof(Method); - - public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); - [ReturnTypeInterceptor] - public virtual object InterceptedMethod() => nameof(InterceptedMethod); - } - - public class BaseCovariantReturnService : CommonService - { - public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); - public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); - - public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); - [ReturnTypeInterceptor] - public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); - } - - public class MidCovariantReturnService : BaseCovariantReturnService - { - public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); - public override MidResult Method() => new(nameof(MidCovariantReturnService)); - - public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); - [ReturnTypeInterceptor] - public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); - } - - public class LeafCovariantReturnService : MidCovariantReturnService - { - public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); - public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); - - public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); - [ReturnTypeInterceptor] - public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); - } - - // this class just does ordinary overriding. - public class OrdinaryOverrideService : LeafCovariantReturnService - { - public override LeafResult Property { get; } = new(nameof(OrdinaryOverrideService)); - public override LeafResult Method() => new(nameof(OrdinaryOverrideService)); - - public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(OrdinaryOverrideService)); - [ReturnTypeInterceptor] - public override LeafResult InterceptedMethod() => new(nameof(OrdinaryOverrideService)); - } - - //this class just inherits from OrdinaryOverrideService, and does not override any members. - public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; -} - From 119bfd110c644fdc3df14d2538d456a6706cdc7b Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 08:31:51 -0230 Subject: [PATCH 50/55] add test cases --- .../CovariantReturnTypeTests.Generic.cs | 205 +++++++++++++++++- .../DynamicProxy/CovariantReturnTypeTests.cs | 72 +++++- 2 files changed, 274 insertions(+), 3 deletions(-) diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs index 3ab5288b..3446ed18 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs @@ -1,5 +1,206 @@ -namespace AspectCore.Tests.DynamicProxy; +using AspectCore.DynamicProxy; +using Xunit; -partial class CovariantReturnTypeTests +namespace AspectCore.Tests.DynamicProxy; + +public partial class CovariantReturnTypeTests { + [Fact] + public void CreateClassProxy_ForGenericMethodCovariantReturn_ShouldUseLeafMethod() + { + var service = ProxyGenerator.CreateClassProxy(); + + AssertTypeValue(service.Convert("value"), v => Assert.Equal(nameof(GenericMethodLeafService), v.Name)); + AssertTypeValue(service.Convert(1), v => Assert.Equal(nameof(GenericMethodLeafService), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForGenericMethodBaseServiceAndLeafImplementation_ShouldUseLeafMethod() + { + var service = ProxyGenerator.CreateClassProxy(); + + AssertTypeValue(service.Convert("value"), v => Assert.Equal(nameof(GenericMethodLeafService), v.Name)); + AssertTypeValue(service.Convert(1), v => Assert.Equal(nameof(GenericMethodLeafService), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForGenericMethodShapeCovariantReturn_ShouldMatchMethodGenericParameterShapes() + { + var service = ProxyGenerator.CreateClassProxy(); + var byRefValue = "value"; + + AssertTypeValue(service.Direct("value"), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); + AssertTypeValue(service.Array(new[] { "value" }), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); + AssertTypeValue(service.List(new System.Collections.Generic.List { "value" }), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); + AssertTypeValue(service.Dictionary(new System.Collections.Generic.Dictionary { ["key"] = "value" }), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); + AssertTypeValue(service.ByRef(ref byRefValue), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForGenericMethodReturnTypeContainingMethodGenericParameter_ShouldUseLeafReturnType() + { + var service = ProxyGenerator.CreateClassProxy(); + + Assert.IsType>(service.ReturnList()); + } + + [Fact] + public void CreateClassProxy_ForGenericTypeCovariantReturn_ShouldUseLeafMethodAndProperty() + { + var service = ProxyGenerator.CreateClassProxy>(); + + AssertTypeValue(service.Direct("value"), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); + AssertTypeValue(service.List(new System.Collections.Generic.List { "value" }), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); + Assert.IsType>(service.ReturnList); + } + + [Fact] + public void CreateClassProxy_ForGenericTypeBaseServiceAndLeafImplementation_ShouldUseLeafMethodAndProperty() + { + var service = ProxyGenerator.CreateClassProxy, TypeGenericShapeLeafService>(); + + AssertTypeValue(service.Direct("value"), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); + AssertTypeValue(service.List(new System.Collections.Generic.List { "value" }), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); + Assert.IsType>(service.ReturnList); + } + + [Fact] + public void CreateInterfaceProxy_ForGenericInterfaceAndCovariantImplementation_ShouldUseBaseResultMethodAndProperty() + { + var service = ProxyGenerator.CreateInterfaceProxy, GenericCovariantReturnService>(); + + AssertTypeValue(service.Property, v => Assert.Equal(nameof(GenericCovariantReturnService), v.Name)); + AssertTypeValue(service.Method("value"), v => Assert.Equal(nameof(GenericCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(GenericCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod("value"), v => Assert.Equal(nameof(GenericCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateInterfaceProxy_ForGenericInterfaceAndLeafImplementation_ShouldUseLeafMethodAndProperty() + { + var service = ProxyGenerator.CreateInterfaceProxy, GenericLeafCovariantReturnService>(); + + AssertTypeValue(service.Property, v => Assert.Equal(nameof(GenericLeafCovariantReturnService), v.Name)); + AssertTypeValue(service.Method("value"), v => Assert.Equal(nameof(GenericLeafCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(GenericLeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod("value"), v => Assert.Equal(nameof(GenericLeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForClosedBaseGenericParameter_ShouldNotBindUnrelatedLeafGenericParameter() + { + var service = ProxyGenerator.CreateClassProxy>(); + var baseService = Assert.IsAssignableFrom>(service); + + AssertTypeValue(baseService.Convert(new BaseResult("base")), v => Assert.Equal(nameof(GenericParameterSubstitutionBaseService), v.Name)); + AssertTypeValue(service.Convert("leaf"), v => Assert.Equal(nameof(GenericParameterSubstitutionLeafService), v.Name)); + } + + public class GenericMethodBaseService + { + public virtual BaseResult Convert(TValue value) => new(nameof(GenericMethodBaseService)); + } + + public class GenericMethodLeafService : GenericMethodBaseService + { + public override LeafResult Convert(TValue value) => new(nameof(GenericMethodLeafService)); + } + + public class GenericMethodShapeBaseService + { + public virtual BaseResult Direct(TValue value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult Array(TValue[] value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult List(System.Collections.Generic.List value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult Dictionary(System.Collections.Generic.Dictionary value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual BaseResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeBaseService)); + + public virtual System.Collections.Generic.IEnumerable ReturnList() => []; + } + + public class GenericMethodShapeLeafService : GenericMethodShapeBaseService + { + public override LeafResult Direct(TValue value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult Array(TValue[] value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult List(System.Collections.Generic.List value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult Dictionary(System.Collections.Generic.Dictionary value) => new(nameof(GenericMethodShapeLeafService)); + + public override LeafResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeLeafService)); + + public override System.Collections.Generic.List ReturnList() => []; + } + + public class TypeGenericShapeBaseService + { + public virtual BaseResult Direct(TValue value) => new(nameof(TypeGenericShapeBaseService)); + + public virtual BaseResult List(System.Collections.Generic.List value) => new(nameof(TypeGenericShapeBaseService)); + + public virtual System.Collections.Generic.IEnumerable ReturnList => []; + } + + public class TypeGenericShapeLeafService : TypeGenericShapeBaseService + { + public override LeafResult Direct(TValue value) => new(nameof(TypeGenericShapeLeafService)); + + public override LeafResult List(System.Collections.Generic.List value) => new(nameof(TypeGenericShapeLeafService)); + + public override System.Collections.Generic.List ReturnList { get; } = []; + } + + public interface IGenericCommonService + { + object Property { get; } + object Method(TValue value); + + object InterceptedProperty { [ReturnTypeInterceptor] get; } + [ReturnTypeInterceptor] + object InterceptedMethod(TValue value); + } + + public class GenericCommonService : IGenericCommonService + { + public virtual object Property { get; } = nameof(Property); + public virtual object Method(TValue value) => nameof(Method); + + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); + [ReturnTypeInterceptor] + public virtual object InterceptedMethod(TValue value) => nameof(InterceptedMethod); + } + + public class GenericCovariantReturnService : GenericCommonService + { + public override BaseResult Property { get; } = new(nameof(GenericCovariantReturnService)); + public override BaseResult Method(TValue value) => new(nameof(GenericCovariantReturnService)); + + public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(GenericCovariantReturnService)); + [ReturnTypeInterceptor] + public override BaseResult InterceptedMethod(TValue value) => new(nameof(GenericCovariantReturnService)); + } + + public class GenericLeafCovariantReturnService : GenericCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(GenericLeafCovariantReturnService)); + public override LeafResult Method(TValue value) => new(nameof(GenericLeafCovariantReturnService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(GenericLeafCovariantReturnService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod(TValue value) => new(nameof(GenericLeafCovariantReturnService)); + } + + public class GenericParameterSubstitutionBaseService + { + public virtual BaseResult Convert(TBase value) => new(nameof(GenericParameterSubstitutionBaseService)); + } + + public class GenericParameterSubstitutionLeafService : GenericParameterSubstitutionBaseService + { + public LeafResult Convert(TLeaf value) => new(nameof(GenericParameterSubstitutionLeafService)); + } } diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs index d12222e2..4abcec08 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using AspectCore.DynamicProxy; using Xunit; @@ -36,6 +36,36 @@ public void CreateClassProxy_ForDerivedCovariantReturnType_ShouldUseOverriddenIn AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } + [Fact] + public void CreateClassProxy_ForLeafCovariantReturnType_ShouldUseLeafInterceptedMembers() + { + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(LeafCovariantReturnService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(LeafCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(LeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(LeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForOrdinaryOverrideAfterCovariantReturnChain_ShouldUseOrdinaryOverrideMembers() + { + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(OrdinaryOverrideService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(OrdinaryOverrideService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForDerivedOrdinaryOverrideAfterCovariantReturnChain_ShouldUseInheritedOrdinaryOverrideMembers() + { + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(OrdinaryOverrideService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(OrdinaryOverrideService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + } + [Fact] public void CreateClassProxy_ForBaseServiceAndCovariantImplementation_ShouldUseStringReturnType() { @@ -66,6 +96,26 @@ public void CreateClassProxy_ForCovariantServiceAndDerivedImplementation_ShouldU AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } + [Fact] + public void CreateClassProxy_ForCovariantServiceAndLeafImplementation_ShouldUseLeafInterceptedMembers() + { + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(LeafCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(LeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(LeafCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(LeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForLeafServiceAndOrdinaryOverrideImplementation_ShouldUseOrdinaryOverrideMembers() + { + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(OrdinaryOverrideService), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(OrdinaryOverrideService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + } + [Fact] public void CreateInterfaceProxy_ForBaseInterfaceAndCovariantImplementation_ShouldUseStringReturnType() { @@ -85,5 +135,25 @@ public void CreateInterfaceProxy_ForBaseInterfaceAndDerivedImplementation_Should AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(MidCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } + + [Fact] + public void CreateInterfaceProxy_ForBaseInterfaceAndLeafImplementation_ShouldUseLeafInterceptedMembers() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(LeafCovariantReturnService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(LeafCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(LeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(LeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateInterfaceProxy_ForBaseInterfaceAndOrdinaryOverrideImplementation_ShouldUseOrdinaryOverrideMembers() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(OrdinaryOverrideService), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(OrdinaryOverrideService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + } } From 1c7b4dff0e3c6696150a4627c57c9db75edc9366 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 08:34:00 -0230 Subject: [PATCH 51/55] refine test cases --- .../CovariantReturnTypeTests.Generic.cs | 4 + .../CovariantReturnTypeTests.TestTypes.cs | 84 ------------------- .../DynamicProxy/CovariantReturnTypeTests.cs | 80 ++++++++++++++++++ 3 files changed, 84 insertions(+), 84 deletions(-) delete mode 100644 tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.TestTypes.cs diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs index 3446ed18..afa149b7 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs @@ -95,7 +95,11 @@ public void CreateClassProxy_ForClosedBaseGenericParameter_ShouldNotBindUnrelate AssertTypeValue(baseService.Convert(new BaseResult("base")), v => Assert.Equal(nameof(GenericParameterSubstitutionBaseService), v.Name)); AssertTypeValue(service.Convert("leaf"), v => Assert.Equal(nameof(GenericParameterSubstitutionLeafService), v.Name)); } +} +// a partial class is used here to separate the test classes from the test methods, for better organization. +partial class CovariantReturnTypeTests +{ public class GenericMethodBaseService { public virtual BaseResult Convert(TValue value) => new(nameof(GenericMethodBaseService)); diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.TestTypes.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.TestTypes.cs deleted file mode 100644 index ad300908..00000000 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.TestTypes.cs +++ /dev/null @@ -1,84 +0,0 @@ -using AspectCore.DynamicProxy; -using System.Threading.Tasks; - -namespace AspectCore.Tests.DynamicProxy; - -partial class CovariantReturnTypeTests -{ - public class ReturnTypeInterceptor : AbstractInterceptorAttribute - { - public override async Task Invoke(AspectContext context, AspectDelegate next) - { - await context.Invoke(next); - - if (context.ReturnValue is BaseResult returnValue) - { - returnValue.Name += nameof(ReturnTypeInterceptor); - } - } - } - - public interface ICommonService - { - object Property { get; } - object Method(); - - object InterceptedProperty { [ReturnTypeInterceptor] get; } - [ReturnTypeInterceptor] - object InterceptedMethod(); - } - - public class CommonService : ICommonService - { - public virtual object Property { get; } = nameof(Property); - public virtual object Method() => nameof(Method); - - public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); - [ReturnTypeInterceptor] - public virtual object InterceptedMethod() => nameof(InterceptedMethod); - } - - public class BaseCovariantReturnService : CommonService - { - public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); - public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); - - public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); - [ReturnTypeInterceptor] - public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); - } - - public class MidCovariantReturnService : BaseCovariantReturnService - { - public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); - public override MidResult Method() => new(nameof(MidCovariantReturnService)); - - public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); - [ReturnTypeInterceptor] - public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); - } - - public class LeafCovariantReturnService : MidCovariantReturnService - { - public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); - public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); - - public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); - [ReturnTypeInterceptor] - public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); - } - - // this class just does ordinary overriding. - public class OrdinaryOverrideService : LeafCovariantReturnService - { - public override LeafResult Property { get; } = new(nameof(OrdinaryOverrideService)); - public override LeafResult Method() => new(nameof(OrdinaryOverrideService)); - - public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(OrdinaryOverrideService)); - [ReturnTypeInterceptor] - public override LeafResult InterceptedMethod() => new(nameof(OrdinaryOverrideService)); - } - - //this class just inherits from OrdinaryOverrideService, and does not override any members. - public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; -} \ No newline at end of file diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs index 4abcec08..e59904e1 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs @@ -157,3 +157,83 @@ public void CreateInterfaceProxy_ForBaseInterfaceAndOrdinaryOverrideImplementati } } +// a partial class is used here to separate the test classes from the test methods, for better organization. +partial class CovariantReturnTypeTests +{ + public class ReturnTypeInterceptor : AbstractInterceptorAttribute + { + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + await context.Invoke(next); + + if (context.ReturnValue is BaseResult returnValue) + { + returnValue.Name += nameof(ReturnTypeInterceptor); + } + } + } + + public interface ICommonService + { + object Property { get; } + object Method(); + + object InterceptedProperty { [ReturnTypeInterceptor] get; } + [ReturnTypeInterceptor] + object InterceptedMethod(); + } + + public class CommonService : ICommonService + { + public virtual object Property { get; } = nameof(Property); + public virtual object Method() => nameof(Method); + + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); + [ReturnTypeInterceptor] + public virtual object InterceptedMethod() => nameof(InterceptedMethod); + } + + public class BaseCovariantReturnService : CommonService + { + public override BaseResult Property { get; } = new(nameof(BaseCovariantReturnService)); + public override BaseResult Method() => new(nameof(BaseCovariantReturnService)); + + public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(BaseCovariantReturnService)); + [ReturnTypeInterceptor] + public override BaseResult InterceptedMethod() => new(nameof(BaseCovariantReturnService)); + } + + public class MidCovariantReturnService : BaseCovariantReturnService + { + public override MidResult Property { get; } = new(nameof(MidCovariantReturnService)); + public override MidResult Method() => new(nameof(MidCovariantReturnService)); + + public override MidResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(MidCovariantReturnService)); + [ReturnTypeInterceptor] + public override MidResult InterceptedMethod() => new(nameof(MidCovariantReturnService)); + } + + public class LeafCovariantReturnService : MidCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(LeafCovariantReturnService)); + public override LeafResult Method() => new(nameof(LeafCovariantReturnService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(LeafCovariantReturnService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(LeafCovariantReturnService)); + } + + // this class just does ordinary overriding. + public class OrdinaryOverrideService : LeafCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(OrdinaryOverrideService)); + public override LeafResult Method() => new(nameof(OrdinaryOverrideService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(OrdinaryOverrideService)); + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(OrdinaryOverrideService)); + } + + //this class just inherits from OrdinaryOverrideService, and does not override any members. + public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; +} \ No newline at end of file From b4b6402eb8cf9cd363e54370fed073a10b1ecb5d Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 08:55:26 -0230 Subject: [PATCH 52/55] fix test cases --- .../DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index a15532de..6fe0c694 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs @@ -256,7 +256,7 @@ private void BuildClassProperties(List properties, List Date: Wed, 8 Jul 2026 10:29:36 -0230 Subject: [PATCH 53/55] add slnx --- AspectCore-Framework.slnx | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 AspectCore-Framework.slnx diff --git a/AspectCore-Framework.slnx b/AspectCore-Framework.slnx new file mode 100644 index 00000000..535b8013 --- /dev/null +++ b/AspectCore-Framework.slnx @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ee0c91db815b28e574ca2a1880ed80a79746c371 Mon Sep 17 00:00:00 2001 From: huoshan12345 Date: Wed, 8 Jul 2026 11:53:17 -0230 Subject: [PATCH 54/55] add test cases --- .../Extensions/ProxyGeneratorExtensions.cs | 2 +- .../Builders/InterfaceImplAstBuilder.cs | 8 +- .../AttributeAspectValidationHandler.cs | 2 +- .../Extensions/TypeExtensions.cs | 3 +- .../CovariantReturnTypeTests.Generic.cs | 108 +++++++-- .../DynamicProxy/CovariantReturnTypeTests.cs | 228 +++++++++++++++++- 6 files changed, 313 insertions(+), 38 deletions(-) diff --git a/src/AspectCore.Core/DynamicProxy/Extensions/ProxyGeneratorExtensions.cs b/src/AspectCore.Core/DynamicProxy/Extensions/ProxyGeneratorExtensions.cs index 7d808f88..0a9e4369 100644 --- a/src/AspectCore.Core/DynamicProxy/Extensions/ProxyGeneratorExtensions.cs +++ b/src/AspectCore.Core/DynamicProxy/Extensions/ProxyGeneratorExtensions.cs @@ -29,7 +29,7 @@ public static TService CreateClassProxy(this IProxyGe { if (proxyGenerator == null) { - throw new ArgumentNullException(nameof(ProxyTypeGenerator)); + throw new ArgumentNullException(nameof(proxyGenerator)); } return (TService)proxyGenerator.CreateClassProxy(typeof(TService), typeof(TImplementation), args ?? ArrayUtils.Empty()); } diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs index ca3f82fa..afc2026c 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs @@ -99,7 +99,7 @@ private ProxyTypeNode BuildStubType(Type[] interfaceTypes) methodConstants: null); } - private MethodNode BuildStubMethod(MethodInfo method) + private static MethodNode BuildStubMethod(MethodInfo method) { var genericParameters = GenericParameterNodeFactory.FromMethod(method); var parameters = ParameterNodeFactory.FromMethod(method); @@ -117,7 +117,7 @@ private MethodNode BuildStubMethod(MethodInfo method) overridesMethod: method); } - private PropertyNode BuildStubProperty(PropertyInfo property) + private static PropertyNode BuildStubProperty(PropertyInfo property) { var backingField = new FieldNode( $"<{property.Name}>k__BackingField", @@ -132,7 +132,7 @@ private PropertyNode BuildStubProperty(PropertyInfo property) getMethod = new MethodNode( property.GetMethod, implementationMethod: null, - property.GetMethod.Name, + property.GetMethod!.Name, MethodBuilderConstants.InterfaceMethodAttributes, new BackingFieldGetBody(backingField.Name), parameters: ParameterNodeFactory.FromMethod(property.GetMethod), @@ -146,7 +146,7 @@ private PropertyNode BuildStubProperty(PropertyInfo property) setMethod = new MethodNode( property.SetMethod, implementationMethod: null, - property.SetMethod.Name, + property.SetMethod!.Name, MethodBuilderConstants.InterfaceMethodAttributes, new BackingFieldSetBody(backingField.Name), parameters: ParameterNodeFactory.FromMethod(property.SetMethod), diff --git a/src/AspectCore.Core/DynamicProxy/ValidationHandlers/AttributeAspectValidationHandler.cs b/src/AspectCore.Core/DynamicProxy/ValidationHandlers/AttributeAspectValidationHandler.cs index aca1a00c..686e6d0a 100644 --- a/src/AspectCore.Core/DynamicProxy/ValidationHandlers/AttributeAspectValidationHandler.cs +++ b/src/AspectCore.Core/DynamicProxy/ValidationHandlers/AttributeAspectValidationHandler.cs @@ -20,7 +20,7 @@ public bool Invoke(AspectValidationContext context, AspectValidationDelegate nex return next(context); } - private bool IsAttributeAspect(MemberInfo member) + private static bool IsAttributeAspect(MemberInfo member) { return member.CustomAttributes.Any(data => typeof(IInterceptor).GetTypeInfo().IsAssignableFrom(data.AttributeType)); } diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs index 476410d9..12a0d15e 100644 --- a/src/AspectCore.Core/Extensions/TypeExtensions.cs +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -418,7 +418,8 @@ public static bool IsAssignableFromGenericTypeDefinition(this Type type, Type ot if (p is null) break; - p = p.GetGenericTypeDefinition(); + if (p.IsConstructedGenericType) + p = p.GetGenericTypeDefinition(); } return false; diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs index afa149b7..71cf303a 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs @@ -1,4 +1,6 @@ -using AspectCore.DynamicProxy; +#pragma warning disable IDE0060 // Remove unused parameter +using AspectCore.DynamicProxy; +using System.Collections.Generic; using Xunit; namespace AspectCore.Tests.DynamicProxy; @@ -30,9 +32,9 @@ public void CreateClassProxy_ForGenericMethodShapeCovariantReturn_ShouldMatchMet var byRefValue = "value"; AssertTypeValue(service.Direct("value"), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); - AssertTypeValue(service.Array(new[] { "value" }), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); - AssertTypeValue(service.List(new System.Collections.Generic.List { "value" }), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); - AssertTypeValue(service.Dictionary(new System.Collections.Generic.Dictionary { ["key"] = "value" }), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); + AssertTypeValue(service.Array(["value"]), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); + AssertTypeValue(service.List(["value"]), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); + AssertTypeValue(service.Dictionary(new Dictionary { ["key"] = "value" }), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); AssertTypeValue(service.ByRef(ref byRefValue), v => Assert.Equal(nameof(GenericMethodShapeLeafService), v.Name)); } @@ -41,7 +43,7 @@ public void CreateClassProxy_ForGenericMethodReturnTypeContainingMethodGenericPa { var service = ProxyGenerator.CreateClassProxy(); - Assert.IsType>(service.ReturnList()); + Assert.IsType>(service.ReturnList()); } [Fact] @@ -50,8 +52,45 @@ public void CreateClassProxy_ForGenericTypeCovariantReturn_ShouldUseLeafMethodAn var service = ProxyGenerator.CreateClassProxy>(); AssertTypeValue(service.Direct("value"), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); - AssertTypeValue(service.List(new System.Collections.Generic.List { "value" }), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); - Assert.IsType>(service.ReturnList); + AssertTypeValue(service.List(["value"]), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); + Assert.IsType>(service.ReturnList); + } + + [Fact] + public void CreateClassProxy_ForGenericLeafCovariantReturnType_ShouldUseLeafMethodAndProperty() + { + var service = ProxyGenerator.CreateClassProxy>(); + + AssertTypeValue(service.Property, v => Assert.Equal(nameof(GenericLeafCovariantReturnService), v.Name)); + AssertTypeValue(service.Method("value"), v => Assert.Equal(nameof(GenericLeafCovariantReturnService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(GenericLeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod("value"), v => Assert.Equal(nameof(GenericLeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForGenericCommonCovariantReturnTypeAndInterfaceView_ShouldUseLeafMethodAndProperty() + { + // CreateClassProxy won't create a proxy for the interface + var service = Assert.IsAssignableFrom>( + ProxyGenerator.CreateClassProxy>()); + + AssertTypeValue(service.Property, v => Assert.Equal(nameof(GenericCommonService), v)); + AssertTypeValue(service.Method("value"), v => Assert.Equal(nameof(GenericCommonService), v)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(GenericCommonService) + nameof(ReturnTypeInterceptor), v)); + AssertTypeValue(service.InterceptedMethod("value"), v => Assert.Equal(nameof(GenericCommonService) + nameof(ReturnTypeInterceptor), v)); + } + + [Fact] + public void CreateClassProxy_ForGenericLeafCovariantReturnTypeAndInterfaceView_ShouldUseLeafMethodAndProperty() + { + var service = Assert.IsAssignableFrom>( + ProxyGenerator.CreateClassProxy>()); + + // CreateClassProxy won't create a proxy for the interface + AssertTypeValue(service.Property, v => Assert.Equal(nameof(GenericCommonService), v)); + AssertTypeValue(service.Method("value"), v => Assert.Equal(nameof(GenericCommonService), v)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(GenericCommonService) + nameof(ReturnTypeInterceptor), v)); + AssertTypeValue(service.InterceptedMethod("value"), v => Assert.Equal(nameof(GenericCommonService) + nameof(ReturnTypeInterceptor), v)); } [Fact] @@ -60,8 +99,8 @@ public void CreateClassProxy_ForGenericTypeBaseServiceAndLeafImplementation_Shou var service = ProxyGenerator.CreateClassProxy, TypeGenericShapeLeafService>(); AssertTypeValue(service.Direct("value"), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); - AssertTypeValue(service.List(new System.Collections.Generic.List { "value" }), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); - Assert.IsType>(service.ReturnList); + AssertTypeValue(service.List(["value"]), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); + Assert.IsType>(service.ReturnList); } [Fact] @@ -95,6 +134,16 @@ public void CreateClassProxy_ForClosedBaseGenericParameter_ShouldNotBindUnrelate AssertTypeValue(baseService.Convert(new BaseResult("base")), v => Assert.Equal(nameof(GenericParameterSubstitutionBaseService), v.Name)); AssertTypeValue(service.Convert("leaf"), v => Assert.Equal(nameof(GenericParameterSubstitutionLeafService), v.Name)); } + + [Fact] + public void CreateClassProxy_ForConstrainedGenericCovariantReturn_ShouldPreserveGenericReturnType() + { + var service = ProxyGenerator.CreateClassProxy(); + var value = new LeafResult("leaf"); + + Assert.Same(value, service.Create(value)); + } + } // a partial class is used here to separate the test classes from the test methods, for better organization. @@ -116,13 +165,13 @@ public class GenericMethodShapeBaseService public virtual BaseResult Array(TValue[] value) => new(nameof(GenericMethodShapeBaseService)); - public virtual BaseResult List(System.Collections.Generic.List value) => new(nameof(GenericMethodShapeBaseService)); + public virtual BaseResult List(List value) => new(nameof(GenericMethodShapeBaseService)); - public virtual BaseResult Dictionary(System.Collections.Generic.Dictionary value) => new(nameof(GenericMethodShapeBaseService)); + public virtual BaseResult Dictionary(Dictionary value) => new(nameof(GenericMethodShapeBaseService)); public virtual BaseResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeBaseService)); - public virtual System.Collections.Generic.IEnumerable ReturnList() => []; + public virtual IEnumerable ReturnList() => []; } public class GenericMethodShapeLeafService : GenericMethodShapeBaseService @@ -131,34 +180,34 @@ public class GenericMethodShapeLeafService : GenericMethodShapeBaseService public override LeafResult Array(TValue[] value) => new(nameof(GenericMethodShapeLeafService)); - public override LeafResult List(System.Collections.Generic.List value) => new(nameof(GenericMethodShapeLeafService)); + public override LeafResult List(List value) => new(nameof(GenericMethodShapeLeafService)); - public override LeafResult Dictionary(System.Collections.Generic.Dictionary value) => new(nameof(GenericMethodShapeLeafService)); + public override LeafResult Dictionary(Dictionary value) => new(nameof(GenericMethodShapeLeafService)); public override LeafResult ByRef(ref TValue value) => new(nameof(GenericMethodShapeLeafService)); - public override System.Collections.Generic.List ReturnList() => []; + public override List ReturnList() => []; } public class TypeGenericShapeBaseService { public virtual BaseResult Direct(TValue value) => new(nameof(TypeGenericShapeBaseService)); - public virtual BaseResult List(System.Collections.Generic.List value) => new(nameof(TypeGenericShapeBaseService)); + public virtual BaseResult List(List value) => new(nameof(TypeGenericShapeBaseService)); - public virtual System.Collections.Generic.IEnumerable ReturnList => []; + public virtual IEnumerable ReturnList => []; } public class TypeGenericShapeLeafService : TypeGenericShapeBaseService { public override LeafResult Direct(TValue value) => new(nameof(TypeGenericShapeLeafService)); - public override LeafResult List(System.Collections.Generic.List value) => new(nameof(TypeGenericShapeLeafService)); + public override LeafResult List(List value) => new(nameof(TypeGenericShapeLeafService)); - public override System.Collections.Generic.List ReturnList { get; } = []; + public override List ReturnList { get; } = []; } - public interface IGenericCommonService + public interface IGenericCommonService { object Property { get; } object Method(TValue value); @@ -170,12 +219,12 @@ public interface IGenericCommonService public class GenericCommonService : IGenericCommonService { - public virtual object Property { get; } = nameof(Property); - public virtual object Method(TValue value) => nameof(Method); + public virtual object Property { get; } = nameof(GenericCommonService); + public virtual object Method(TValue value) => nameof(GenericCommonService); - public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(GenericCommonService); [ReturnTypeInterceptor] - public virtual object InterceptedMethod(TValue value) => nameof(InterceptedMethod); + public virtual object InterceptedMethod(TValue value) => nameof(GenericCommonService); } public class GenericCovariantReturnService : GenericCommonService @@ -207,4 +256,15 @@ public class GenericParameterSubstitutionLeafService : GenericParameterSu { public LeafResult Convert(TLeaf value) => new(nameof(GenericParameterSubstitutionLeafService)); } + + public class ConstrainedGenericReturnBaseService + { + public virtual BaseResult Create(TValue value) where TValue : LeafResult + => value; + } + + public class ConstrainedGenericReturnLeafService : ConstrainedGenericReturnBaseService + { + public override TValue Create(TValue value) => value; + } } diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs index e59904e1..354c2717 100644 --- a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs @@ -46,6 +46,24 @@ public void CreateClassProxy_ForLeafCovariantReturnType_ShouldUseLeafIntercepted AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(LeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); } + [Fact] + public void CreateClassProxy_ForLeafCovariantReturnTypeAndInterfaceMethodView_ShouldUseLeafInterceptedMethod() + { + var service = Assert.IsAssignableFrom(ProxyGenerator.CreateClassProxy()); + + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(CommonService), v)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(CommonService) + nameof(ReturnTypeInterceptor), v)); + } + + [Fact] + public void CreateClassProxy_ForLeafCovariantReturnTypeAndInterfacePropertyView_ShouldUseLeafInterceptedProperty() + { + var service = Assert.IsAssignableFrom(ProxyGenerator.CreateClassProxy()); + + AssertTypeValue(service.Property, v => Assert.Equal(nameof(CommonService), v)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(CommonService) + nameof(ReturnTypeInterceptor), v)); + } + [Fact] public void CreateClassProxy_ForOrdinaryOverrideAfterCovariantReturnChain_ShouldUseOrdinaryOverrideMembers() { @@ -155,6 +173,62 @@ public void CreateInterfaceProxy_ForBaseInterfaceAndOrdinaryOverrideImplementati AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(OrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); } + + [Fact] + public void CreateClassProxy_ForMethodOnlyOrdinaryOverrideAfterCovariantReturnChain_ShouldUseOrdinaryOverrideMethod() + { + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(MethodOnlyOrdinaryOverrideService), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(MethodOnlyOrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForPropertyOnlyOrdinaryOverrideAfterCovariantReturnChain_ShouldUseOrdinaryOverrideProperty() + { + var service = ProxyGenerator.CreateClassProxy(); + AssertTypeValue(service.Property, v => Assert.Equal(nameof(PropertyOnlyOrdinaryOverrideService), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(PropertyOnlyOrdinaryOverrideService) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForInvariantGenericReturnHiddenMembers_ShouldNotTreatMembersAsCovariantOverrides() + { + var service = ProxyGenerator.CreateClassProxy(); + + Assert.IsType>(service.Create()); + Assert.IsType>(service.Items); + } + + [Fact] + public void CreateInterfaceProxy_ForCovariantInterface_ShouldUseLeafInterfaceMembers() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + + AssertTypeValue(service.Property, v => Assert.Equal(nameof(CovariantInterfaceLeafImplementation), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(CovariantInterfaceLeafImplementation), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(CovariantInterfaceLeafImplementation) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(CovariantInterfaceLeafImplementation) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateInterfaceProxy_ForBaseCovariantInterfaceAndLeafImplementation_ShouldUseLeafImplementationMembers() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + + AssertTypeValue(service.Property, v => Assert.Equal(nameof(CovariantInterfaceLeafImplementation), v.Name)); + AssertTypeValue(service.Method(), v => Assert.Equal(nameof(CovariantInterfaceLeafImplementation), v.Name)); + AssertTypeValue(service.InterceptedProperty, v => Assert.Equal(nameof(CovariantInterfaceLeafImplementation) + nameof(ReturnTypeInterceptor), v.Name)); + AssertTypeValue(service.InterceptedMethod(), v => Assert.Equal(nameof(CovariantInterfaceLeafImplementation) + nameof(ReturnTypeInterceptor), v.Name)); + } + + [Fact] + public void CreateClassProxy_ForIndexerCovariantReturn_ShouldUseLeafIndexerGetter() + { + var service = ProxyGenerator.CreateClassProxy(); + + AssertTypeValue(service[0], v => Assert.Equal(nameof(IndexerLeafCovariantReturnService), v.Name)); + AssertTypeValue(service["key"], v => Assert.Equal(nameof(IndexerLeafCovariantReturnService) + nameof(ReturnTypeInterceptor), v.Name)); + } } // a partial class is used here to separate the test classes from the test methods, for better organization. @@ -166,9 +240,18 @@ public override async Task Invoke(AspectContext context, AspectDelegate next) { await context.Invoke(next); - if (context.ReturnValue is BaseResult returnValue) + switch (context.ReturnValue) { - returnValue.Name += nameof(ReturnTypeInterceptor); + case BaseResult returnValue: + { + returnValue.Name += nameof(ReturnTypeInterceptor); + break; + } + case string str: + { + context.ReturnValue = str + nameof(ReturnTypeInterceptor); + break; + } } } } @@ -185,12 +268,12 @@ public interface ICommonService public class CommonService : ICommonService { - public virtual object Property { get; } = nameof(Property); - public virtual object Method() => nameof(Method); + public virtual object Property { get; } = nameof(CommonService); + public virtual object Method() => nameof(CommonService); - public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(CommonService); [ReturnTypeInterceptor] - public virtual object InterceptedMethod() => nameof(InterceptedMethod); + public virtual object InterceptedMethod() => nameof(CommonService); } public class BaseCovariantReturnService : CommonService @@ -236,4 +319,135 @@ public class OrdinaryOverrideService : LeafCovariantReturnService //this class just inherits from OrdinaryOverrideService, and does not override any members. public class DerivedOrdinaryOverrideService : OrdinaryOverrideService; -} \ No newline at end of file + + public class MethodOnlyCommonService + { + public virtual object Method() => nameof(Method); + + [ReturnTypeInterceptor] + public virtual object InterceptedMethod() => nameof(InterceptedMethod); + } + + public class MethodOnlyBaseCovariantReturnService : MethodOnlyCommonService + { + public override BaseResult Method() => new(nameof(MethodOnlyBaseCovariantReturnService)); + + [ReturnTypeInterceptor] + public override BaseResult InterceptedMethod() => new(nameof(MethodOnlyBaseCovariantReturnService)); + } + + public class MethodOnlyLeafCovariantReturnService : MethodOnlyBaseCovariantReturnService + { + public override LeafResult Method() => new(nameof(MethodOnlyLeafCovariantReturnService)); + + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(MethodOnlyLeafCovariantReturnService)); + } + + public class MethodOnlyOrdinaryOverrideService : MethodOnlyLeafCovariantReturnService + { + public override LeafResult Method() => new(nameof(MethodOnlyOrdinaryOverrideService)); + + [ReturnTypeInterceptor] + public override LeafResult InterceptedMethod() => new(nameof(MethodOnlyOrdinaryOverrideService)); + } + + public class PropertyOnlyCommonService + { + public virtual object Property { get; } = nameof(Property); + + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(InterceptedProperty); + } + + public class PropertyOnlyBaseCovariantReturnService : PropertyOnlyCommonService + { + public override BaseResult Property { get; } = new(nameof(PropertyOnlyBaseCovariantReturnService)); + + public override BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(PropertyOnlyBaseCovariantReturnService)); + } + + public class PropertyOnlyLeafCovariantReturnService : PropertyOnlyBaseCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(PropertyOnlyLeafCovariantReturnService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(PropertyOnlyLeafCovariantReturnService)); + } + + public class PropertyOnlyOrdinaryOverrideService : PropertyOnlyLeafCovariantReturnService + { + public override LeafResult Property { get; } = new(nameof(PropertyOnlyOrdinaryOverrideService)); + + public override LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(PropertyOnlyOrdinaryOverrideService)); + } + + public class InvariantGenericReturnBaseService + { + public virtual System.Collections.Generic.List Items { get; } = []; + + public virtual System.Collections.Generic.List Create() => []; + } + + public class InvariantGenericReturnLeafService : InvariantGenericReturnBaseService + { + public new System.Collections.Generic.List Items { get; } = []; + + public new System.Collections.Generic.List Create() => []; + } + + public interface ICovariantInterfaceBaseService + { + BaseResult Property { get; } + BaseResult Method(); + + BaseResult InterceptedProperty { [ReturnTypeInterceptor] get; } + [ReturnTypeInterceptor] + BaseResult InterceptedMethod(); + } + + public interface ICovariantInterfaceLeafService : ICovariantInterfaceBaseService + { + new LeafResult Property { get; } + new LeafResult Method(); + + new LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } + [ReturnTypeInterceptor] + new LeafResult InterceptedMethod(); + } + + public class CovariantInterfaceLeafImplementation : ICovariantInterfaceLeafService + { + public LeafResult Property { get; } = new(nameof(CovariantInterfaceLeafImplementation)); + public LeafResult Method() => new(nameof(CovariantInterfaceLeafImplementation)); + + public LeafResult InterceptedProperty { [ReturnTypeInterceptor] get; } = new(nameof(CovariantInterfaceLeafImplementation)); + [ReturnTypeInterceptor] + public LeafResult InterceptedMethod() => new(nameof(CovariantInterfaceLeafImplementation)); + + BaseResult ICovariantInterfaceBaseService.Property => Property; + BaseResult ICovariantInterfaceBaseService.Method() => Method(); + + BaseResult ICovariantInterfaceBaseService.InterceptedProperty => InterceptedProperty; + BaseResult ICovariantInterfaceBaseService.InterceptedMethod() => InterceptedMethod(); + } + + public class IndexerCommonService + { + public virtual object this[int index] => nameof(IndexerCommonService); + + public virtual object this[string key] { [ReturnTypeInterceptor] get => nameof(IndexerCommonService); } + } + + public class IndexerBaseCovariantReturnService : IndexerCommonService + { + public override BaseResult this[int index] => new(nameof(IndexerBaseCovariantReturnService)); + + public override BaseResult this[string key] { [ReturnTypeInterceptor] get => new(nameof(IndexerBaseCovariantReturnService)); } + } + + public class IndexerLeafCovariantReturnService : IndexerBaseCovariantReturnService + { + public override LeafResult this[int index] => new(nameof(IndexerLeafCovariantReturnService)); + + public override LeafResult this[string key] { [ReturnTypeInterceptor] get => new(nameof(IndexerLeafCovariantReturnService)); } + } +} From 51945e0b4086e9412b84c7296eea67aff0d2ce14 Mon Sep 17 00:00:00 2001 From: Haoyang Liu Date: Sun, 12 Jul 2026 23:39:18 +0800 Subject: [PATCH 55/55] fix: ServiceInterceptor AllowMultiple not respected due to .Distinct() Remove .Distinct() from InterceptorCollector.Collect() because HandleMultiple() already correctly handles deduplication: - Interceptors with AllowMultiple=true are kept as-is (no dedup) - Interceptors with AllowMultiple=false are deduplicated by type The .Distinct() call was incorrectly deduplicating ServiceInterceptorAttribute instances that wrap the same inner interceptor type, because ServiceInterceptorAttribute.Equals() compares by _interceptorType. This caused ServiceInterceptor to effectively ignore AllowMultiple=true. Fixes #325 --- .../DynamicProxy/InterceptorCollector.cs | 2 +- .../Integrate/ServiceInterceptorTests.cs | 35 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/AspectCore.Core/DynamicProxy/InterceptorCollector.cs b/src/AspectCore.Core/DynamicProxy/InterceptorCollector.cs index 28dd752a..53f3119f 100644 --- a/src/AspectCore.Core/DynamicProxy/InterceptorCollector.cs +++ b/src/AspectCore.Core/DynamicProxy/InterceptorCollector.cs @@ -60,7 +60,7 @@ public IEnumerable Collect(MethodInfo serviceMethod, MethodInfo im return HandleInjector(CollectFromService(serviceMethod). Concat(CollectFromAdditionalSelector(serviceMethod, implementationMethod)). HandleSort(). - HandleMultiple()).Distinct().ToArray(); + HandleMultiple()).ToArray(); }); } diff --git a/tests/AspectCore.Tests/Integrate/ServiceInterceptorTests.cs b/tests/AspectCore.Tests/Integrate/ServiceInterceptorTests.cs index e5f92ff3..60f1e493 100644 --- a/tests/AspectCore.Tests/Integrate/ServiceInterceptorTests.cs +++ b/tests/AspectCore.Tests/Integrate/ServiceInterceptorTests.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using AspectCore.Configuration; using AspectCore.DynamicProxy; using AspectCore.DependencyInjection; using Xunit; @@ -9,16 +8,30 @@ namespace AspectCore.Tests.Integrate public class ServiceInterceptorTests : IntegrateTestBase { [Fact] - public void Service_Interceptor_Tests() + public void Service_Interceptor_AllowMultiple_Tests() { var service = ServiceResolver.Resolve(); - Assert.Equal(1, service.Foo()); + // ServiceInterceptorAttribute.AllowMultiple = true. + // Both [ServiceInterceptor(typeof(Test))] on interface and class should execute. + // Before fix: .Distinct() deduplicated them (same _interceptorType) → result = 1. + // After fix: both execute → result = 2. + Assert.Equal(2, service.Foo()); + } + + [Fact] + public void Service_Interceptor_Single_Attribute_Tests() + { + var service = ServiceResolver.Resolve(); + // Only class has [ServiceInterceptor], interface has none. + // Single interceptor executes → result = 1. + Assert.Equal(1, service.Bar()); } protected override void ConfigureService(IServiceContext serviceContext) { serviceContext.AddType(); - serviceContext.AddType(); + serviceContext.AddType(); + serviceContext.AddType(); } public class Test : AbstractInterceptor @@ -45,5 +58,19 @@ public virtual int Foo() return 0; } } + + public interface IProxySingle + { + int Bar(); + } + + [ServiceInterceptor(typeof(Test))] + public class ProxySingle : IProxySingle + { + public virtual int Bar() + { + return 0; + } + } } }