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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/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/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/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/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index a4101c31..6fe0c694 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 { @@ -123,33 +124,82 @@ 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(); + 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 body = MethodBodyFactory.DecideBody(method, implMethod, _aspectValidator, _serviceType); + var (covariantReturnMethod, skip) = FindCovariantReturnMethod(method); + if (skip) + continue; - 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 (serviceMethod, implMethod) = covariantReturnMethod is null + ? (method, InterfaceImplBuilder.ResolveImplementationMethod(method, _implType)) + : (covariantReturnMethod, covariantReturnMethod); - var node = InterfaceImplBuilder.BuildProxyMethod( - method, implMethod, method.Name, attributes, body, null, methodConstants); + var node = CreateClassProxyMethodNode(serviceMethod, implMethod, null, methodConstants); methods.Add(node); } + + (MethodInfo, bool Skip) FindCovariantReturnMethod(MethodInfo method) + { + var covariantReturn = covariantReturnMethods + .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); + + if (method.DeclaringType == method.ReflectedType) + { + // the 'method' is declared in the _serviceType, and it is overridden by a covariant-return method in the _implType. + // In this case, use CovariantReturnMethod for both serviceMethod and implMethod. + return (covariantReturn.CovariantReturnMethod, false); + } + else + { + // the 'method' is declared in a base class of the _serviceType, and it is overridden by a covariant-return method in the _implType. + // In this case, the covariant-return method will be visited in a later iteration when the base class is processed, so skip the current method to avoid conflicts. + return (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()) @@ -160,38 +210,29 @@ private void BuildClassProperties(List properties, List @@ -202,6 +243,44 @@ private void BuildClassProperties(List properties, List getter.IsOverriddenByCovariantReturnMethod(m.CovariantReturnMethod)) + .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method + .FirstOrDefault(); + + var overridden = covariantReturn.OverriddenMethod; + if (overridden == null) + return (null, false); + + if (getter.DeclaringType == getter.ReflectedType) + { + // the 'getter' is declared in the _serviceType, and it is overridden by a covariant-return method in the _implType. + // In this case, use CovariantReturnMethod for both serviceMethod and implMethod. + return (covariantReturn.CovariantReturnMethod, false); + } + else + { + // the 'getter' is declared in a base class of the _serviceType, and it is overridden by a covariant-return method in the _implType. + // In this case, the covariant-return method will be visited in a later iteration when the base class is processed, so skip the current method to avoid conflicts. + return (null, true); + } + } } 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 cbc824ba..afc2026c 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 { @@ -98,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); @@ -116,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", @@ -131,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), @@ -145,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), @@ -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,8 +270,9 @@ 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)); + interfaceType, MethodBuilderConstants.InterfaceMethodAttributes, methods, methodConstants, covariantReturnGetter)); } // Additional interface properties (explicit) @@ -275,10 +280,32 @@ 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)); } } + + MethodInfo FindCovariantReturnMethod(MethodInfo interfaceMethod) + { + return covariantReturnMethods + .Where(m => m.InterfaceDeclarations.Contains(interfaceMethod)) + .OrderByDescending(m => m.InheritanceDepth) // find most concrete covariant return method + .FirstOrDefault() + .CovariantReturnMethod; + } + + MethodInfo FindCovariantReturnGetter(PropertyInfo property) + { + 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; + } } private static PropertyNode BuildProxyProperty( @@ -289,7 +316,8 @@ private static PropertyNode BuildProxyProperty( Type serviceType, MethodAttributes methodAttrs, List methods, - List methodConstants) + List methodConstants, + MethodInfo covariantReturnGetter) { MethodNode getMethod = null; MethodNode setMethod = null; @@ -297,7 +325,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/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; 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/CollectionExtensions.cs b/src/AspectCore.Core/Extensions/CollectionExtensions.cs new file mode 100644 index 00000000..dfc13e6c --- /dev/null +++ b/src/AspectCore.Core/Extensions/CollectionExtensions.cs @@ -0,0 +1,15 @@ +// ReSharper disable once CheckNamespace +#pragma warning disable IDE0130 // Namespace does not match folder structure +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 +} \ No newline at end of file diff --git a/src/AspectCore.Core/Extensions/EnumerableExtensions.cs b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..0d60b6d8 --- /dev/null +++ b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs @@ -0,0 +1,24 @@ +#if NETSTANDARD2_0 || NETSTANDARD2_1 +using System.Collections.Generic; +#endif + +// ReSharper disable once CheckNamespace +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Linq; + +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..6acde303 --- /dev/null +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using static AspectCore.Extensions.TypeExtensions; + +namespace AspectCore.Extensions; + +internal static class MethodInfoExtensions +{ + public static IEnumerable GetInterfaceDeclarations(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; + } + } + } + + /// + /// 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; + + 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(); + } + + public static bool IsConstructedGenericMethod(this MethodInfo method) + { +#if NETSTANDARD2_0 + 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 new file mode 100644 index 00000000..2d46e4e9 --- /dev/null +++ b/src/AspectCore.Core/Extensions/ObjectExtensions.cs @@ -0,0 +1,16 @@ +#nullable enable +namespace AspectCore.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 new file mode 100644 index 00000000..12a0d15e --- /dev/null +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -0,0 +1,468 @@ +#nullable enable +#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; + +namespace AspectCore.Extensions; + +[DebuggerDisplay("({OverriddenMethod.ReturnType.Name} {OverriddenMethod.Name}) -> ({CovariantReturnMethod.ReturnType.Name} {CovariantReturnMethod.Name})")] +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) + { + 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); + + /// + /// 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 overriddenMethod = otherMethods.FirstOrDefault(m => m.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + if (overriddenMethod is null) + continue; + + var interfaceDeclarations = covariantReturnMethod.GetInterfaceDeclarations().ToHashSet(); + result.Add(new CovariantReturnMethodInfo(covariantReturnMethod, overriddenMethod, interfaceDeclarations)); + } + + return result; + } + + /// + /// 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 is not null) + { + depth++; + current = current.BaseType; + } + + return depth - 1; // 去掉 object 自己那一层 + } + + /// + /// 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.IsInCovariantReturnChain() == false) + return false; + + if (covariantReturnMethod.Name != method.Name) + return false; + + if (method.DeclaringType is not { } dt1 + || covariantReturnMethod.DeclaringType is not { } dt2 + || dt1.IsCovariantReturnAssignableFrom(dt2) == false) + return 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 (methodReturnType.IsCovariantReturnAssignableFrom(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)) + { + var parameterType = p2.ParameterType.SubstituteGenericParameters(genericParameterMap); + if (p1.ParameterType.IsCovariantReturnEquivalentTo(parameterType) == false) + return false; + } + + var isGeneric = covariantReturnMethod.IsGenericMethod; + if (isGeneric != method.IsGenericMethod) + return false; + + if (method.IsGenericMethodDefinition != covariantReturnMethod.IsGenericMethodDefinition) + 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.IsCovariantReturnEquivalentTo(a2.SubstituteGenericParameters(genericParameterMap)) == false) + return false; + } + } + + 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) + { + if (type.GetArrayRank() != other.GetArrayRank()) + return false; + + // ReSharper disable once TailRecursiveCall + return argumentComparer(type.GetElementType()!, other.GetElementType()!); + } + + if (type.IsGenericType == false || other.IsGenericType == false) + return false; + + if (type.IsConstructedGenericType != other.IsConstructedGenericType) + return false; + + if (type.IsGenericTypeDefinition != other.IsGenericTypeDefinition) + return false; + + var args1 = type.GetGenericArguments(); + var args2 = other.GetGenericArguments(); + + if (args1.Length != args2.Length) + return false; + + var d1 = type.GetGenericTypeDefinition(); + var d2 = other.GetGenericTypeDefinition(); + + if (typeDefinitionComparer(d1, d2) == false) + return false; + + foreach (var (a1, a2) in args1.Zip(args2)) + { + if (argumentComparer(a1, a2) == false) + return false; + } + + return true; + } + + /// + /// 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; + return variance == GenericParameterAttributes.Covariant; + } + + private static bool AreEquivalentGenericParameters(Type type, Type other) + { + if (type.IsGenericParameter == false || other.IsGenericParameter == false) + return false; + + if (type.GenericParameterPosition != other.GenericParameterPosition) + return false; + + if (type.DeclaringMethod != other.DeclaringMethod) + return false; + + return type.DeclaringType == other.DeclaringType; + } + + public static bool IsAssignableFromGenericTypeDefinition(this Type type, Type other) + { + if (type.IsGenericTypeDefinition == false) + return false; + + var p = other; + while (true) + { + if (type.IsAssignableFrom(p)) + return true; + + if (p.IsGenericTypeDefinition == false) + return false; + + foreach (var it in p.GetInterfaces()) + { + if (it.IsGenericType == false) + continue; + + if (type.IsAssignableFrom(it.GetGenericTypeDefinition())) + return true; + } + + p = p.BaseType; + + if (p is null) + break; + + if (p.IsConstructedGenericType) + p = p.GetGenericTypeDefinition(); + } + + return false; + } + + private static bool TryUnwrapByRef(ref Type type, ref Type other) + { + if (type.IsByRef != other.IsByRef) + return false; + + if (type.IsByRef == false) + return true; + + if (other.IsByRef == false) + return false; + + type = type.GetElementType()!; + other = other.GetElementType()!; + return true; + } + + public static bool IsCovariantReturnAssignableFrom(this Type type, Type other) + { + if (TryUnwrapByRef(ref type, ref other) == false) + return false; + + return type.IsAssignableFrom(other) + || type.IsAssignableFromGenericTypeDefinition(other) + || AreEquivalentGenericParameters(type, other) + || 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) + { + if (TryUnwrapByRef(ref type, ref other) == false) + return false; + + return type == other + || AreEquivalentGenericParameters(type, other) + || AreEquivalentGenericTypes(type, other, IsCovariantReturnEquivalentTo, (a, b) => a == b); + } +} diff --git a/src/AspectCore.Core/Properties/AssemblyInfo.cs b/src/AspectCore.Core/Properties/AssemblyInfo.cs index 28b81503..49a44664 100644 --- a/src/AspectCore.Core/Properties/AssemblyInfo.cs +++ b/src/AspectCore.Core/Properties/AssemblyInfo.cs @@ -28,3 +28,14 @@ "e15b6849fbabea83fc9b8b6abf959e606f5e51b268a6a6c2d4757bbc3ae33689373faaedf61077" + "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.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/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/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.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.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); } } } 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(); diff --git a/tests/AspectCore.Tests/AspectCore.Tests.csproj b/tests/AspectCore.Tests/AspectCore.Tests.csproj index 0debc9ad..79de9924 100644 --- a/tests/AspectCore.Tests/AspectCore.Tests.csproj +++ b/tests/AspectCore.Tests/AspectCore.Tests.csproj @@ -1,7 +1,9 @@  + net9.0;net8.0;net7.0;net6.0 false + 13.0 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/DynamicProxy/CovariantReturnTypeTests.Generic.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs new file mode 100644 index 00000000..71cf303a --- /dev/null +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.Generic.cs @@ -0,0 +1,270 @@ +#pragma warning disable IDE0060 // Remove unused parameter +using AspectCore.DynamicProxy; +using System.Collections.Generic; +using Xunit; + +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(["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)); + } + + [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(["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] + public void CreateClassProxy_ForGenericTypeBaseServiceAndLeafImplementation_ShouldUseLeafMethodAndProperty() + { + var service = ProxyGenerator.CreateClassProxy, TypeGenericShapeLeafService>(); + + AssertTypeValue(service.Direct("value"), v => Assert.Equal(nameof(TypeGenericShapeLeafService), v.Name)); + AssertTypeValue(service.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)); + } + + [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. +partial class CovariantReturnTypeTests +{ + 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(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 { 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(GenericCommonService); + public virtual object Method(TValue value) => nameof(GenericCommonService); + + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(GenericCommonService); + [ReturnTypeInterceptor] + public virtual object InterceptedMethod(TValue value) => nameof(GenericCommonService); + } + + 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)); + } + + 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 new file mode 100644 index 00000000..354c2717 --- /dev/null +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypeTests.cs @@ -0,0 +1,453 @@ +using System; +using System.Threading.Tasks; +using AspectCore.DynamicProxy; +using Xunit; + +namespace AspectCore.Tests.DynamicProxy; + +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. + /// + private static void AssertTypeValue(object value, Action action) + { + var v = Assert.IsType(value); + action(v); + } + + [Fact] + public void CreateClassProxy_ForCovariantReturnType_ShouldUseStringReturnType() + { + var service = ProxyGenerator.CreateClassProxy(); + 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 => 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_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_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() + { + 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() + { + var service = ProxyGenerator.CreateClassProxy(); + 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 => 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 => 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 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() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + 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 => 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 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)); + } + + [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. +partial class CovariantReturnTypeTests +{ + public class ReturnTypeInterceptor : AbstractInterceptorAttribute + { + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + await context.Invoke(next); + + switch (context.ReturnValue) + { + case BaseResult returnValue: + { + returnValue.Name += nameof(ReturnTypeInterceptor); + break; + } + case string str: + { + context.ReturnValue = str + nameof(ReturnTypeInterceptor); + break; + } + } + } + } + + 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(CommonService); + public virtual object Method() => nameof(CommonService); + + public virtual object InterceptedProperty { [ReturnTypeInterceptor] get; } = nameof(CommonService); + [ReturnTypeInterceptor] + public virtual object InterceptedMethod() => nameof(CommonService); + } + + 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; + + 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)); } + } +} 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/IsOverriddenByCovariantReturnMethodTests.cs b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs new file mode 100644 index 00000000..8b899ba8 --- /dev/null +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/IsOverriddenByCovariantReturnMethodTests.cs @@ -0,0 +1,448 @@ +#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; +using Xunit; +using Xunit.Abstractions; +using static AspectCore.Tests.Extensions.TypeExtensionsTests.TestTypes; + +namespace AspectCore.Tests.Extensions.TypeExtensionsTests; + +public class IsOverriddenByCovariantReturnMethodTests(ITestOutputHelper output) +{ + [Fact] + public void ShouldReturnTrue_WhenMethodIsOverriddenWithCovariantReturnType() + { + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var covariantReturnMethod = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnFalse_WhenMethodNamesDiffer() + { + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var covariantReturnMethod = GetPropertyGetter(nameof(BaseCovariantReturnService.Property), typeof(BaseResult)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnFalse_WhenReturnTypesAreTheSame() + { + var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + var covariantReturnMethod = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnFalse_WhenReturnTypeIsNotAssignable() + { + var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + var covariantReturnMethod = GetMethod(nameof(CommonService.Method), typeof(object)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnFalse_WhenParametersDiffer() + { + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var covariantReturnMethod = GetMethod(nameof(GenericMethodLeafService.Convert), typeof(LeafResult), parameterCount: 1); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnTrue_WhenGenericMethodIsOverriddenWithCovariantReturnType() + { + 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)); + } + + [Fact] + public void ShouldReturnFalse_WhenBothArgumentsAreSameMethodInfoInstance() + { + var method = GetMethod(nameof(BaseCovariantReturnService.Method), typeof(BaseResult)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(method)); + } + + [Fact] + public void 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 ShouldReturnFalse_WhenSecondMethodIsOrdinaryOverride() + { + var method = GetMethod(nameof(CommonService.Method), typeof(object)); + var ordinaryOverrideMethod = GetMethod(nameof(OrdinaryOverrideService.Method), typeof(object)); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(ordinaryOverrideMethod)); + } + + [Fact] + public void ShouldReturnTrue_WhenFinalOrdinaryOverrideMatchesBaseResultMethodReflectedFromLeafType() + { + 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.True(method.IsOverriddenByCovariantReturnMethod(ordinaryOverrideMethod)); + } + + [Fact] + public void 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 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 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 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 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 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 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 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 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 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 ShouldReturnTrue_WhenGenericMethodReturnTypeContainsMethodGenericParameter() + { + var method = GetGenericReturnMethod(nameof(GenericMethodShapeBaseService.ReturnList), typeof(IEnumerable<>)); + var covariantReturnMethod = GetGenericReturnMethod(nameof(GenericMethodShapeLeafService.ReturnList), typeof(List<>)); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + 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); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + 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); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnTrue_WhenReturnTypeContainsTypeGenericParameter() + { + 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)); + } + + [Fact] + 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); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + 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); + + Assert.True(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + 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); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void 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 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); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + 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); + + Assert.False(method.IsOverriddenByCovariantReturnMethod(covariantReturnMethod)); + } + + [Fact] + public void ShouldReturnTrue_WhenCovariantReturnUsesConstrainedGenericParameter() + { + var method = GetMethod(nameof(ConstrainedGenericReturnBaseService.Create), typeof(BaseResult), parameterCount: 1); + var covariantReturnMethod = GetMethod(nameof(ConstrainedGenericReturnLeafService.Create), + m => m.ReturnType.IsGenericParameter && m.GetParameters().Length == 1); + + 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_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(List)); + var covariantReturnMethod = GetMethod(nameof(InvariantGenericReturnLeafService.Create), + m => m.DeclaringType == typeof(InvariantGenericReturnLeafService) && m.ReturnType == typeof(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); + } + + 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) + .Single(property => property.Name == name && property.PropertyType == propertyType) + .GetMethod!; + } + + [Theory] + [InlineData(typeof(MixedGenericShapeLeafService<>))] + public void GetMethods_Print(Type type) + { + output.WriteLine($"{type.Name}'s Methods:"); + + var methods = type.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 isCrt = method.IsCovariantReturnMethod(); + + var attributeNames = attributes.Select(a => a.AttributeType.Name); + output.WriteLine($"[{dt?.Name}.{method.Name}] Return Type: {method.ReturnType.Name}, Covariant Return: {isCrt}, Attributes: {string.Join(", ", attributeNames)}"); + } + } + + [Theory] + [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) + { + output.WriteLine($"[{param.Name}]Declaring Type: {param.DeclaringType?.Name}, Declaring Method: {param.DeclaringMethod?.Name}"); + } + + var method = type.GetMethod(methodName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + Assert.NotNull(method); + + output.WriteLine(""); + output.WriteLine("Method 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 new file mode 100644 index 00000000..9ba1f067 --- /dev/null +++ b/tests/AspectCore.Tests/Extensions/TypeExtensionsTests/TestTypes.cs @@ -0,0 +1,283 @@ +#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; + +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; + } + + 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 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() => []; + } + + 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)); + } + + 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(); +} 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; + } + } } }