Skip to content

Commit ee52db7

Browse files
committed
support interface proxy
1 parent 4d508bd commit ee52db7

File tree

5 files changed

+125
-23
lines changed

5 files changed

+125
-23
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
// ReSharper disable once CheckNamespace
5+
namespace AspectCore.Extensions
6+
{
7+
internal static class EnumerableExtensions
8+
{
9+
#if NETSTANDARD2_0 || NETSTANDARD2_1
10+
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second)
11+
{
12+
return first.Zip(second, (f, s) => (f, s));
13+
}
14+
#endif
15+
16+
#if NETSTANDARD2_0
17+
public static HashSet<TSource> ToHashSet<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer = null)
18+
{
19+
return new HashSet<TSource>(source, comparer);
20+
}
21+
#endif
22+
}
23+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Reflection;
4+
5+
// ReSharper disable once CheckNamespace
6+
namespace AspectCore.Extensions
7+
{
8+
internal static class MethodInfoExtensions
9+
{
10+
public static IEnumerable<MethodInfo> GetInterfaceDeclarationsForMethod(this MethodInfo method)
11+
{
12+
var typeInfo = method.ReflectedType?.GetTypeInfo();
13+
if (typeInfo is null)
14+
yield break;
15+
16+
foreach (var implementedInterface in typeInfo.ImplementedInterfaces)
17+
{
18+
var map = typeInfo.GetInterfaceMap(implementedInterface);
19+
foreach (var (interfaceMethod, targetMethod) in map.InterfaceMethods.Zip(map.TargetMethods))
20+
{
21+
if (targetMethod == method)
22+
yield return interfaceMethod;
23+
}
24+
}
25+
}
26+
}
27+
}
28+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using AspectCore.Extensions;
6+
7+
// ReSharper disable once CheckNamespace
8+
namespace AspectCore.Utils
9+
{
10+
// NOTE:
11+
// For class proxy: We just define the covariant return methods in the implementation type like normal methods, the CLR will handle the propagation.
12+
// For interface proxy: We need to use the covariant return methods as the interface methods' implementation.
13+
internal partial class ProxyGeneratorUtils
14+
{
15+
private static readonly Type PreserveBaseOverridesAttribute = Type.GetType("System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", false);
16+
17+
// key: covariant return method
18+
// value: overridden method's interface declarations
19+
internal static IReadOnlyDictionary<MethodInfo, HashSet<MethodInfo>> GetCovariantReturnMethodMap(Type implType)
20+
{
21+
var result = new Dictionary<MethodInfo, HashSet<MethodInfo>>();
22+
// No PreserveBaseOverridesAttribute means that the runtime does not support covariant return types.
23+
if (PreserveBaseOverridesAttribute is null)
24+
return result;
25+
26+
const MethodAttributes attributes = MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot;
27+
var covariantReturnMethods = implType
28+
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
29+
.Where(m => (m.Attributes & attributes) == attributes)
30+
.Where(m => m.IsDefined(PreserveBaseOverridesAttribute))
31+
.ToHashSet();
32+
33+
foreach (var method in covariantReturnMethods)
34+
{
35+
var interfaceDeclarations = method.GetInterfaceDeclarationsForMethod().ToHashSet();
36+
result[method] = interfaceDeclarations;
37+
}
38+
39+
return result;
40+
}
41+
}
42+
}

src/AspectCore.Core/Utils/ProxyGeneratorUtils.cs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace AspectCore.Utils
1616
{
17-
internal class ProxyGeneratorUtils
17+
internal partial class ProxyGeneratorUtils
1818
{
1919
private const string ProxyNameSpace = "AspectCore.DynamicGenerated";
2020
private const string ProxyAssemblyName = "AspectCore.DynamicProxy.Generator";
@@ -392,15 +392,18 @@ internal static MethodBuilder DefineInterfaceImplMethod(MethodInfo method, TypeB
392392

393393
internal static void DefineInterfaceProxyMethods(Type interfaceType, Type targetType, Type[] additionalInterfaces, TypeDesc typeDesc)
394394
{
395+
var covariantReturnMethodMap = GetCovariantReturnMethodMap(targetType);
395396
foreach (var method in interfaceType.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding()))
396397
{
397-
DefineInterfaceMethod(method, targetType, typeDesc);
398+
var covariantReturnMethod = covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(method)).Key;
399+
DefineInterfaceMethod(method, targetType, typeDesc, covariantReturnMethod);
398400
}
399401
foreach (var item in additionalInterfaces)
400402
{
401403
foreach (var method in item.GetTypeInfo().DeclaredMethods.Where(x => !x.IsPropertyBinding()))
402404
{
403-
DefineExplicitMethod(method, targetType, typeDesc);
405+
var covariantReturnMethod = covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(method)).Key;
406+
DefineExplicitMethod(method, targetType, typeDesc, covariantReturnMethod);
404407
}
405408
}
406409
}
@@ -421,16 +424,16 @@ internal static void DefineClassProxyMethods(Type serviceType, Type implType, Ty
421424
}
422425
}
423426

424-
internal static MethodBuilder DefineInterfaceMethod(MethodInfo method, Type implType, TypeDesc typeDesc)
427+
internal static MethodBuilder DefineInterfaceMethod(MethodInfo method, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null)
425428
{
426-
var methodBuilder = DefineMethod(method, method.Name, InterfaceMethodAttributes, implType, typeDesc);
429+
var methodBuilder = DefineMethod(method, method.Name, InterfaceMethodAttributes, implType, typeDesc, covariantReturnMethod);
427430
typeDesc.Builder.DefineMethodOverride(methodBuilder, method);
428431
return methodBuilder;
429432
}
430433

431-
internal static MethodBuilder DefineExplicitMethod(MethodInfo method, Type implType, TypeDesc typeDesc)
434+
internal static MethodBuilder DefineExplicitMethod(MethodInfo method, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null)
432435
{
433-
var methodBuilder = DefineMethod(method, method.GetName(), ExplicitMethodAttributes, implType, typeDesc);
436+
var methodBuilder = DefineMethod(method, method.GetName(), ExplicitMethodAttributes, implType, typeDesc, covariantReturnMethod);
434437
typeDesc.Builder.DefineMethodOverride(methodBuilder, method);
435438
return methodBuilder;
436439
}
@@ -454,6 +457,7 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType
454457
attributes |= MethodAttributes.FamORAssem;
455458
}
456459

460+
// NewSlot is required for covariant return types.
457461
if (method.Attributes.HasFlag(MethodAttributes.NewSlot))
458462
{
459463
attributes |= MethodAttributes.NewSlot;
@@ -463,7 +467,7 @@ internal static MethodBuilder DefineClassMethod(MethodInfo method, Type implType
463467
return methodBuilder;
464468
}
465469

466-
private static MethodBuilder DefineMethod(MethodInfo method, string name, MethodAttributes attributes, Type implType, TypeDesc typeDesc)
470+
private static MethodBuilder DefineMethod(MethodInfo method, string name, MethodAttributes attributes, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnMethod = null)
467471
{
468472
var methodBuilder = typeDesc.Builder.DefineMethod(name, attributes, method.CallingConvention, method.ReturnType, method.GetParameterTypes());
469473

@@ -475,16 +479,13 @@ private static MethodBuilder DefineMethod(MethodInfo method, string name, Method
475479
//inherit targetMethod's attribute
476480
foreach (var customAttributeData in method.CustomAttributes)
477481
{
478-
if (customAttributeData.AttributeType.Name == "PreserveBaseOverridesAttribute")
479-
continue; // Skip PreserveBaseOverridesAttribute as it is not needed in dynamic proxy generation.
480-
481482
methodBuilder.SetCustomAttribute(CustomAttributeBuilderUtils.DefineCustomAttribute(customAttributeData));
482483
}
483484

484485
//define paramters
485486
ParameterBuilderUtils.DefineParameters(method, methodBuilder);
486487

487-
var implementationMethod = implType.GetTypeInfo().GetMethodBySignature(method);
488+
var implementationMethod = covariantReturnMethod ?? implType.GetTypeInfo().GetMethodBySignature(method);
488489
if (implementationMethod == null)
489490
{
490491
var interfaces = implType.GetInterfaces();
@@ -616,7 +617,7 @@ void EmitProxyMethodBody()
616617
ilGen.Emit(OpCodes.Callvirt, MethodUtils.CreateAspectActivator);
617618
ilGen.Emit(OpCodes.Ldloc, activatorContext);
618619

619-
EmitReturnVaule(ilGen);
620+
EmitReturnValue(ilGen);
620621

621622
if (method.ReturnType != typeof(void))
622623
{
@@ -707,7 +708,7 @@ void EmitInitializeMetaData(ILGenerator ilGen)
707708
}
708709
}
709710

710-
void EmitReturnVaule(ILGenerator ilGen)
711+
void EmitReturnValue(ILGenerator ilGen)
711712
{
712713
if (method.ReturnType == typeof(void))
713714
{
@@ -744,17 +745,25 @@ private class PropertyBuilderUtils
744745
{
745746
public static void DefineInterfaceProxyProperties(Type interfaceType, Type implType, Type[] additionalInterfaces, TypeDesc typeDesc)
746747
{
748+
var covariantReturnMethodMap = GetCovariantReturnMethodMap(implType);
749+
747750
foreach (var property in interfaceType.GetTypeInfo().DeclaredProperties)
748751
{
749752
var builder = DefineInterfaceProxyProperty(property, property.Name, implType, typeDesc);
750-
DefineInterfacePropertyMethod(builder, property, implType, typeDesc);
753+
var covariantReturnGetter = property.CanRead
754+
? covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(property.GetMethod)).Key
755+
: null;
756+
DefineInterfacePropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter);
751757
}
752758
foreach (var item in additionalInterfaces)
753759
{
754760
foreach (var property in item.GetTypeInfo().DeclaredProperties)
755761
{
756762
var builder = DefineInterfaceProxyProperty(property, property.GetDisplayName(), implType, typeDesc);
757-
DefineExplicitPropertyMethod(builder, property, implType, typeDesc);
763+
var covariantReturnGetter = property.CanRead
764+
? covariantReturnMethodMap.FirstOrDefault(m => m.Value.Contains(property.GetMethod)).Key
765+
: null;
766+
DefineExplicitPropertyMethod(builder, property, implType, typeDesc, covariantReturnGetter);
758767
}
759768
}
760769
}
@@ -793,11 +802,11 @@ private static void DefineClassPropertyMethod(PropertyBuilder propertyBuilder, P
793802
}
794803
}
795804

796-
private static void DefineInterfacePropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc)
805+
private static void DefineInterfacePropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnGetter = null)
797806
{
798807
if (property.CanRead)
799808
{
800-
var method = MethodBuilderUtils.DefineInterfaceMethod(property.GetMethod, implType, typeDesc);
809+
var method = MethodBuilderUtils.DefineInterfaceMethod(property.GetMethod, implType, typeDesc, covariantReturnGetter);
801810
propertyBuilder.SetGetMethod(method);
802811
}
803812
if (property.CanWrite)
@@ -807,11 +816,11 @@ private static void DefineInterfacePropertyMethod(PropertyBuilder propertyBuilde
807816
}
808817
}
809818

810-
private static void DefineExplicitPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc)
819+
private static void DefineExplicitPropertyMethod(PropertyBuilder propertyBuilder, PropertyInfo property, Type implType, TypeDesc typeDesc, MethodInfo covariantReturnGetter = null)
811820
{
812821
if (property.CanRead)
813822
{
814-
var method = MethodBuilderUtils.DefineExplicitMethod(property.GetMethod, implType, typeDesc);
823+
var method = MethodBuilderUtils.DefineExplicitMethod(property.GetMethod, implType, typeDesc, covariantReturnGetter);
815824
propertyBuilder.SetGetMethod(method);
816825
}
817826
if (property.CanWrite)

tests/AspectCore.Tests/DynamicProxy/CovariantReturnTypesTests.cs renamed to tests/AspectCore.Tests/DynamicProxy/CovariantReturnMethodTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
using System.Reflection;
1+
using System.Linq;
22
using AspectCore.DynamicProxy;
33
using Xunit;
44

55
namespace AspectCore.Tests.DynamicProxy;
66

7-
public class CovariantReturnTypesTests : DynamicProxyTestBase
7+
public class CovariantReturnMethodTests : DynamicProxyTestBase
88
{
99
public interface IService
1010
{
@@ -20,7 +20,7 @@ public class Service : IService
2020

2121
public class CovariantReturnsService : Service
2222
{
23-
public override string Method() => nameof(CovariantReturnsService);
23+
public sealed override string Method() => nameof(CovariantReturnsService);
2424
public override string Property { get; } = nameof(CovariantReturnsService);
2525
}
2626

0 commit comments

Comments
 (0)