diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/ClassProxyAstBuilder.cs index a4101c31..e6108d1c 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,75 @@ 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.FirstOrDefault(m => m.OverriddenMethod.IsSameBaseDefinition(method)); + var overridden = covariantReturn.OverriddenMethod; + if (overridden == null) + return (null, false); + + // If the current method is the base definition of a covariant-return override chain, + // the actual covariant-return method will not appear in _serviceType's method list. + // In that case, use CovariantReturnMethod instead. + // + // Otherwise, the covariant-return method will be visited in a later iteration, + // so skip the current method to avoid duplicate processing. + return overridden.GetBaseDefinition() == method + ? (covariantReturn.CovariantReturnMethod, false) + : (null, true); + } } private void BuildClassProperties(List properties, List methods, List methodConstants) { + var covariantReturnMethods = _implType.GetCovariantReturnMethods(); + foreach (var property in _serviceType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { if (!property.IsVisibleAndVirtual()) @@ -160,38 +203,29 @@ private void BuildClassProperties(List properties, List @@ -202,6 +236,42 @@ private void BuildClassProperties(List properties, List IsOverriddenByCovariantReturnProperty(property, m)); + var overridden = covariantReturn.OverriddenMethod; + if (overridden == null) + return (null, false); + + // If the property's getter is the base definition of a covariant-return override chain, + // the actual covariant-return getter will not appear in _serviceType's property list. + // In that case, use CovariantReturnMethod instead. + // + // Otherwise, the covariant-return property will be visited in a later iteration, + // so skip the current property to avoid duplicate processing. + return overridden.GetBaseDefinition() == property.GetMethod + ? (covariantReturn.CovariantReturnMethod, false) + : (null, true); + } + + bool IsOverriddenByCovariantReturnProperty(PropertyInfo property, CovariantReturnMethodInfo info) + { + // this case occurs when the property is not overridden in the serviceType. + var get = property.GetMethod; + if (info.OverriddenMethod.IsSameBaseDefinition(get)) + return true; + + // this case occurs when the property is overridden in the serviceType. + // in this case, the property type is super class of (and not the same as) the getter's type. + var covariantReturn = info.CovariantReturnMethod; + if (covariantReturn.IsSameBaseDefinition(get) + && covariantReturn.ReturnType != property.PropertyType + && property.PropertyType.IsAssignableFrom(covariantReturn.ReturnType)) + return true; + + return false; + } } private void BuildAdditionalInterfaceMembers(List methods, List properties, List methodConstants) diff --git a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs index cbc824ba..a92c6931 100644 --- a/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs +++ b/src/AspectCore.Core/DynamicProxy/ProxyBuilder/Builders/InterfaceImplAstBuilder.cs @@ -6,6 +6,7 @@ using AspectCore.Utils; using AspectCore.Extensions.Reflection; using AspectCore.DynamicProxy.ProxyBuilder.Nodes; +using AspectCore.Extensions; namespace AspectCore.DynamicProxy.ProxyBuilder.Builders { @@ -239,11 +240,13 @@ internal static void BuildInterfaceProxyMembers( List methodConstants) { var resolvedImplType = implType ?? interfaceType; + var covariantReturnMethods = resolvedImplType.GetCovariantReturnMethods(); // Primary interface methods foreach (var method in interfaceType.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var implMethod = ResolveImplementationMethod(method, resolvedImplType); + var covariantReturnMethod = FindCovariantReturnMethod(method); + var implMethod = covariantReturnMethod ?? ResolveImplementationMethod(method, resolvedImplType); var body = MethodBodyFactory.DecideBody(method, implMethod, aspectValidator, interfaceType); var node = BuildProxyMethod(method, implMethod, method.Name, MethodBuilderConstants.InterfaceMethodAttributes, body, method, methodConstants); @@ -255,7 +258,8 @@ internal static void BuildInterfaceProxyMembers( { foreach (var method in iface.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding())) { - var implMethod = ResolveImplementationMethod(method, resolvedImplType); + var covariantReturnMethod = FindCovariantReturnMethod(method); + var implMethod = covariantReturnMethod ?? ResolveImplementationMethod(method, resolvedImplType); var body = MethodBodyFactory.DecideBody(method, implMethod, aspectValidator, interfaceType); var node = BuildProxyMethod(method, implMethod, method.GetName(), MethodBuilderConstants.ExplicitMethodAttributes, body, method, methodConstants); @@ -266,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,25 @@ 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 + .FirstOrDefault(m => m.InterfaceDeclarations.Contains(interfaceMethod)) + .CovariantReturnMethod; + } + + MethodInfo FindCovariantReturnGetter(PropertyInfo property) + { + return property.CanRead + ? covariantReturnMethods.FirstOrDefault(m => m.InterfaceDeclarations.Contains(property.GetMethod)).CovariantReturnMethod + : null; + } } private static PropertyNode BuildProxyProperty( @@ -289,7 +309,8 @@ private static PropertyNode BuildProxyProperty( Type serviceType, MethodAttributes methodAttrs, List methods, - List methodConstants) + List methodConstants, + MethodInfo covariantReturnGetter) { MethodNode getMethod = null; MethodNode setMethod = null; @@ -297,7 +318,7 @@ private static PropertyNode BuildProxyProperty( if (property.CanRead) { var method = property.GetMethod; - var implMethod = ResolveImplementationMethod(method, implType); + var implMethod = covariantReturnGetter ?? ResolveImplementationMethod(method, implType); var body = MethodBodyFactory.DecideBody(method, implMethod, aspectValidator, serviceType); var overrides = methodAttrs == MethodBuilderConstants.ExplicitMethodAttributes ? method : method; getMethod = BuildProxyMethod(method, implMethod, methodAttrs == MethodBuilderConstants.ExplicitMethodAttributes ? method.GetName() : method.Name, diff --git a/src/AspectCore.Core/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/Extensions/CollectionExtensions.cs b/src/AspectCore.Core/Extensions/CollectionExtensions.cs new file mode 100644 index 00000000..cd6d1249 --- /dev/null +++ b/src/AspectCore.Core/Extensions/CollectionExtensions.cs @@ -0,0 +1,15 @@ +// ReSharper disable once CheckNamespace +namespace System.Collections.Generic +{ + internal static class CollectionExtensions + { +#if NETSTANDARD2_0 + public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue) + { + return dictionary.TryGetValue(key, out var obj) + ? obj + : defaultValue; + } +#endif + } +} diff --git a/src/AspectCore.Core/Extensions/EnumerableExtensions.cs b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..0706d89f --- /dev/null +++ b/src/AspectCore.Core/Extensions/EnumerableExtensions.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +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..7d511297 --- /dev/null +++ b/src/AspectCore.Core/Extensions/MethodInfoExtensions.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using static AspectCore.Extensions.TypeExtensions; + +// ReSharper disable once CheckNamespace +namespace AspectCore.Extensions +{ + internal static class MethodInfoExtensions + { + public static 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(); + } + } +} + diff --git a/src/AspectCore.Core/Extensions/TypeExtensions.cs b/src/AspectCore.Core/Extensions/TypeExtensions.cs new file mode 100644 index 00000000..0d9538be --- /dev/null +++ b/src/AspectCore.Core/Extensions/TypeExtensions.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +// ReSharper disable once CheckNamespace +namespace AspectCore.Extensions +{ + internal readonly struct CovariantReturnMethodInfo + { + /// + /// The method that defines the covariant return type — + /// i.e., the overriding method that returns a more derived type. + /// + public readonly MethodInfo CovariantReturnMethod; + + /// + /// Gets the method that is overridden or implemented by . + /// + /// + /// This is **reflected from the derived type**, not necessarily + /// the base definition returned by . + /// + /// In other words, it represents the version of the base or interface method as seen + /// through the derived class’s reflection context, which may differ from the canonical + /// base definition when covariant return types are involved. + /// + /// + public readonly MethodInfo 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; + } + } + + 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 => Match(covariantReturnMethod, m)); + if (overriddenMethod is null) + continue; + + var interfaceDeclarations = covariantReturnMethod.GetInterfaceDeclarations().ToHashSet(); + result.Add(new CovariantReturnMethodInfo(covariantReturnMethod, overriddenMethod, interfaceDeclarations)); + } + + return result; + + bool Match(MethodInfo covariantReturnMethod, MethodInfo other) + { + if (covariantReturnMethod.Name != other.Name) + return false; + + // return types should not be the same. + if (covariantReturnMethod.ReturnType == other.ReturnType) + return false; + + if (other.ReturnType.IsAssignableFrom(covariantReturnMethod.ReturnType) == false) + return false; + + var params1 = covariantReturnMethod.GetParameters(); + var params2 = other.GetParameters(); + + if (params1.Length != params2.Length) + return false; + + foreach (var (p1, p2) in params1.Zip(params2)) + { + if (p1.ParameterType != p2.ParameterType) + return false; + } + + var isGeneric = covariantReturnMethod.IsGenericMethod; + if (isGeneric != other.IsGenericMethod) + return false; + + if (isGeneric) + { + var args1 = covariantReturnMethod.GetGenericArguments(); + var args2 = other.GetGenericArguments(); + if (args1.Length != args2.Length) + return false; + + foreach (var (a1, a2) in args1.Zip(args2)) + { + if (a1 != a2) + return false; + } + } + + return true; + } + } + } +} diff --git a/src/AspectCore.Core/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..9bd440e4 100644 --- a/tests/AspectCore.Tests/AspectCore.Tests.csproj +++ b/tests/AspectCore.Tests/AspectCore.Tests.csproj @@ -1,4 +1,5 @@  + net9.0;net8.0;net7.0;net6.0 false diff --git a/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs new file mode 100644 index 00000000..cae72fab --- /dev/null +++ b/tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs @@ -0,0 +1,133 @@ +using System.Threading.Tasks; +using AspectCore.DynamicProxy; +using Xunit; + +namespace AspectCore.Tests.DynamicProxy; + +public class CovariantReturnMethodTests : DynamicProxyTestBase +{ + public class Interceptor : AbstractInterceptorAttribute + { + public override async Task Invoke(AspectContext context, AspectDelegate next) + { + await context.Invoke(next); + + var returnType = context.ImplementationMethod.ReturnType; + if (returnType == typeof(string)) + { + context.ReturnValue += nameof(Interceptor); + } + else if (returnType == typeof(object)) + { + context.ReturnValue = nameof(Interceptor); + } + } + } + + public interface IService + { + object Property { get; } + object Method(); + + object ProxyProperty { [Interceptor] get; } + [Interceptor] + object ProxyMethod(); + } + + public class Service : IService + { + public virtual object Property { get; } = 1; + public virtual object Method() => 1; + + public virtual object ProxyProperty { [Interceptor] get; } = new(); + [Interceptor] + public virtual object ProxyMethod() => new(); + } + + public class CovariantReturnsService : Service + { + public override string Property { get; } = nameof(CovariantReturnsService); + public override string Method() => nameof(CovariantReturnsService); + + public override string ProxyProperty { [Interceptor] get; } = nameof(CovariantReturnsService); + [Interceptor] + public override string ProxyMethod() => nameof(CovariantReturnsService); + } + + public class DerivedCovariantReturnsService : CovariantReturnsService + { + public override string ProxyProperty { [Interceptor] get; } = nameof(DerivedCovariantReturnsService); + [Interceptor] + public override string ProxyMethod() => nameof(DerivedCovariantReturnsService); + } + + [Fact] + public void CreateClassProxy_CovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + } + + [Fact] + public void CreateClassProxy_DerivedCovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + } + + [Fact] + public void CreateClassProxy_Service_CovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + } + + [Fact] + public void CreateClassProxy_Service_DerivedCovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + } + + [Fact] + public void CreateClassProxy_CovariantReturnsService_DerivedCovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateClassProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + } + + [Fact] + public void CreateInterfaceProxy_IService_CovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(CovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + } + + [Fact] + public void CreateInterfaceProxy_IService_DerivedCovariantReturnsService_Test() + { + var service = ProxyGenerator.CreateInterfaceProxy(); + Assert.Equal(nameof(CovariantReturnsService), service.Method()); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyMethod()); + Assert.Equal(nameof(CovariantReturnsService), service.Property); + Assert.Equal(nameof(DerivedCovariantReturnsService) + nameof(Interceptor), service.ProxyProperty); + } +} diff --git a/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs b/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs new file mode 100644 index 00000000..61381ca7 --- /dev/null +++ b/tests/AspectCore.Tests/Extensions/CovariantReturnTests.cs @@ -0,0 +1,131 @@ +using System.Linq; +using System.Reflection; +using AspectCore.Extensions; +using Xunit; + +namespace AspectCore.Tests.Extensions; + +public class CovariantReturnTests +{ + // Basic covariant override between classes + private class Animal { } + + private class Dog : Animal { } + + private class BaseClass + { + public virtual Animal Make() => new(); + } + + private class DerivedClass : BaseClass + { + public override Dog Make() => new(); + } + + [Fact] + public void FindsBasicCovariantReturnMethod() + { + var methods = typeof(DerivedClass).GetCovariantReturnMethods(); + Assert.Single(methods); + + var info = methods.Single(); + Assert.Equal(nameof(DerivedClass.Make), info.CovariantReturnMethod.Name); + Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); + Assert.Equal(typeof(Animal), info.OverriddenMethod.ReturnType); + Assert.True(info.OverriddenMethod.DeclaringType == typeof(DerivedClass) + || info.OverriddenMethod.DeclaringType == typeof(BaseClass)); + } + + // Multi-level inheritance + private class B1 { public virtual B1 Clone() => new(); } + + private class B2 : B1 { public override B2 Clone() => new(); } + + private class B3 : B2 { } + + [Fact] + public void DoesNotReportEmptyForDeeperHierarchy() + { + var methods = typeof(B3).GetCovariantReturnMethods(); + // Should inherit from B2 → B1, so no new covariant method + Assert.Single(methods); + + methods = typeof(B2).GetCovariantReturnMethods(); + Assert.Single(methods); + } + + // Interface with covariant return + private interface IFactory { T Create(); } + + private class Widget { } + + private class FancyWidget : Widget { } + + private class WidgetFactory : IFactory + { + public virtual Widget Create() => new(); + } + + private class FancyWidgetFactory : WidgetFactory, IFactory + { + public override FancyWidget Create() => new(); + } + + [Fact] + public void HandlesInterfaceCovariantReturnCorrectly() + { + var info = typeof(FancyWidgetFactory).GetCovariantReturnMethods().Single(); + Assert.Equal(typeof(FancyWidget), info.CovariantReturnMethod.ReturnType); + Assert.Equal(typeof(Widget), info.OverriddenMethod.ReturnType); + // Should list IFactory.Create() as an interface declaration + Assert.Contains(info.InterfaceDeclarations, m => m.DeclaringType!.GetGenericTypeDefinition() == typeof(IFactory<>)); + } + + // Explicit interface implementation + private interface ICreator { T Create(); } + + private class Creator : ICreator + { + Animal ICreator.Create() => new(); + } + + private class DogCreator : Creator, ICreator + { + Dog ICreator.Create() => new(); + } + + [Fact] + public void FindsExplicitInterfaceImplementation() + { + var infos = typeof(DogCreator).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .Where(m => m.Name.Contains("ICreator")) + .SelectMany(m => m.GetInterfaceDeclarations()) + .ToList(); + Assert.NotEmpty(infos); + Assert.All(infos, i => Assert.Equal("Create", i.Name)); + } + + // 5Generic method covariance + private class GenBase { public virtual T Build() => default!; } + + private class GenDerived : GenBase { public override Dog Build() => new(); } + + [Fact] + public void WorksForGenericCovariantReturn() + { + var info = typeof(GenDerived).GetCovariantReturnMethods().Single(); + Assert.Equal(typeof(Dog), info.CovariantReturnMethod.ReturnType); + Assert.Empty(info.CovariantReturnMethod.GetGenericArguments()); + Assert.Empty(info.OverriddenMethod.GetGenericArguments()); + Assert.NotEqual(info.OverriddenMethod, info.OverriddenMethod.GetBaseDefinition()); + } + + // Same slot verification + [Fact] + public void IsSameBaseDefinition_CovariantReturn() + { + var m1 = typeof(DerivedClass).GetMethod(nameof(DerivedClass.Make))!; + var m2 = typeof(BaseClass).GetMethod(nameof(BaseClass.Make))!; + Assert.False(m1.IsSameBaseDefinition(m2)); + } +}