diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c205052b..d737add665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Enhancements: - Two new generic method overloads `proxyGenerator.CreateClassProxy([options], constructorArguments, interceptors)` (@backstromjoel, #636) - Allow specifying which attributes should always be copied to proxy class by adding attribute type to `AttributesToAlwaysReplicate`. Previously only non-inherited, with `Inherited=false`, attributes were copied. (@shoaibshakeel381, #633) +- Support for C# 8+ [default interface methods](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods) in interface and class proxies without target (@stakx, #661) Bugfixes: - `ArgumentException`: "Could not find method overriding method" with overridden class method having generic by-ref parameter (@stakx, #657) diff --git a/buildscripts/common.props b/buildscripts/common.props index 157f3f4433..e7f541ce4d 100644 --- a/buildscripts/common.props +++ b/buildscripts/common.props @@ -1,7 +1,7 @@ - 9.0 + 11.0 $(NoWarn);CS1591;CS3014;CS3003;CS3001;CS3021 $(NoWarn);CS0612;CS0618 git diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/DefaultInterfaceMembersTestCase.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/DefaultInterfaceMembersTestCase.cs index 8df2d01834..cc4838a593 100644 --- a/src/Castle.Core.Tests/DynamicProxy.Tests/DefaultInterfaceMembersTestCase.cs +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/DefaultInterfaceMembersTestCase.cs @@ -16,6 +16,9 @@ using System.Reflection; +using Castle.DynamicProxy.Tests.Interceptors; +using Castle.DynamicProxy.Tests.Interfaces; + using NUnit.Framework; namespace Castle.DynamicProxy.Tests @@ -23,6 +26,351 @@ namespace Castle.DynamicProxy.Tests [TestFixture] public class DefaultInterfaceMembersTestCase : BasePEVerifyTestCase { + #region Methods with default implementation + + [Test] + public void Can_proxy_class_that_inherits_method_with_default_implementation_from_interface() + { + _ = generator.CreateClassProxy(); + } + + [Test] + public void Can_proxy_interface_with_method_with_default_implementation() + { + _ = generator.CreateInterfaceProxyWithoutTarget(); + } + + [Test] + public void Default_implementation_gets_called_when_method_not_intercepted_in_proxied_class() + { + var options = new ProxyGenerationOptions(new ProxyNothingHook()); + var proxy = generator.CreateClassProxy(options); + var expected = "default implementation"; + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Default_implementation_gets_called_when_method_not_intercepted_in_proxied_interface() + { + var options = new ProxyGenerationOptions(new ProxyNothingHook()); + var proxy = generator.CreateInterfaceProxyWithoutTarget(options); + var expected = "default implementation"; + var actual = proxy.MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_intercept_method_with_default_implementation_in_proxied_class() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateClassProxy(interceptor); + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_intercept_method_with_default_implementation_in_proxied_interface() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + var actual = proxy.MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_method_default_implementation_in_proxied_class() + { + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateClassProxy(interceptor); + var expected = "default implementation"; + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_method_default_implementation_in_proxied_interface() + { + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + var expected = "default implementation"; + var actual = proxy.MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + #endregion + + #region Generic methods with default implementation + + [Test] + public void Can_proceed_to_generic_method_default_implementation_in_proxied_interface() + { + // (This test targets the code regarding generics inside `InterfaceProxyWithoutTargetContributor.CreateCallbackMethod`.) + + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + var expected = "default implementation for Int32"; + var actual = proxy.GenericMethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + #endregion + + #region Methods with "overridden" default implementation + + [Test] + public void Can_intercept_method_with_overridden_default_implementation_in_proxied_class() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateClassProxy(interceptor); + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + #endregion + + #region Siblings of methods with default implementation + + [Test] + public void Can_intercept_method_that_is_sibling_of_method_with_default_implementation_in_proxied_class() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateClassProxy(interceptor); + var actual = proxy.Method(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_intercept_method_that_is_sibling_of_method_with_default_implementation_in_proxied_interface() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + var actual = proxy.Method(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_base_implementation_of_method_that_is_sibling_of_method_with_default_implementation_in_proxied_class() + { + var expected = "class implementation"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateClassProxy(interceptor); + var actual = proxy.Method(); + Assert.AreEqual(expected, actual); + } + + #endregion + + #region Properties with default implementation + + [Test] + public void Can_proxy_class_that_inherits_property_with_default_implementation_from_interface() + { + _ = generator.CreateClassProxy(); + } + + [Test] + public void Can_proxy_interface_with_property_with_default_implementation() + { + _ = generator.CreateInterfaceProxyWithoutTarget(); + } + + [Test] + public void Default_implementation_gets_called_when_property_not_intercepted_in_proxied_class() + { + var options = new ProxyGenerationOptions(new ProxyNothingHook()); + var proxy = generator.CreateClassProxy(options); + var expected = "default implementation"; + var actual = ((IHavePropertyWithDefaultImplementation)proxy).PropertyWithDefaultImplementation; + Assert.AreEqual(expected, actual); + } + + [Test] + public void Default_implementation_gets_called_when_property_not_intercepted_in_proxied_interface() + { + var options = new ProxyGenerationOptions(new ProxyNothingHook()); + var proxy = generator.CreateInterfaceProxyWithoutTarget(options); + var expected = "default implementation"; + var actual = proxy.PropertyWithDefaultImplementation; + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_intercept_property_with_default_implementation_in_proxied_class() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateClassProxy(interceptor); + var actual = ((IHavePropertyWithDefaultImplementation)proxy).PropertyWithDefaultImplementation; + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_intercept_property_with_default_implementation_in_proxied_interface() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + var actual = proxy.PropertyWithDefaultImplementation; + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_property_default_implementation_in_proxied_class() + { + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateClassProxy(interceptor); + var expected = "default implementation"; + var actual = ((IHavePropertyWithDefaultImplementation)proxy).PropertyWithDefaultImplementation; + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_property_default_implementation_in_proxied_interface() + { + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + var expected = "default implementation"; + var actual = proxy.PropertyWithDefaultImplementation; + Assert.AreEqual(expected, actual); + } + + #endregion + + #region Methods with default implementation in additional interfaces + + [Test] + public void Can_proxy_class_that_inherits_method_with_default_implementation_from_additional_interface() + { + _ = generator.CreateClassProxy(typeof(object), new[] { typeof(IHaveMethodWithDefaultImplementation) }); + } + + [Test] + public void Can_proxy_interface_with_method_with_default_implementation_from_additional_interface() + { + _ = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), new[] { typeof(IHaveMethodWithDefaultImplementation) }); + } + + [Test] + public void Can_intercept_method_with_default_implementation_from_additional_interface_in_proxied_class() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateClassProxy(typeof(object), new[] { typeof(IHaveMethodWithDefaultImplementation) },interceptor); + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_intercept_method_with_default_implementation_from_additional_interface_in_proxied_interface() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), new[] { typeof(IHaveMethodWithDefaultImplementation) },interceptor); + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_method_default_implementation_from_additional_interface_in_proxied_class() + { + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateClassProxy(typeof(object), new[] { typeof(IHaveMethodWithDefaultImplementation) }, interceptor); + var expected = "default implementation"; + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_method_default_implementation_from_additional_interface_in_proxied_interface() + { + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), new[] { typeof(IHaveMethodWithDefaultImplementation) }, interceptor); + var expected = "default implementation"; + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + #endregion + + #region Non-public methods with default implementation + + public void Can_proxy_class_that_inherits_protected_method_with_default_implementation_from_interface() + { + _ = generator.CreateClassProxy(); + } + + [Test] + public void Can_proxy_interface_with_protected_method_with_default_implementation() + { + _ = generator.CreateInterfaceProxyWithoutTarget(); + } + + [Test] + public void Can_intercept_protected_method_with_default_implementation_in_proxied_class() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => + { + Assume.That(invocation.Method.Name == "ProtectedMethodWithDefaultImplementation"); + invocation.ReturnValue = expected; + }); + var proxy = generator.CreateClassProxy(interceptor); + var actual = ((IHaveProtectedMethodWithDefaultImplementation)proxy).InvokeProtectedMethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_intercept_protected_method_with_default_implementation_in_proxied_interface() + { + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => + { + Assume.That(invocation.Method.Name == "ProtectedMethodWithDefaultImplementation"); + invocation.ReturnValue = expected; + }); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + var actual = proxy.InvokeProtectedMethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_protected_method_default_implementation_in_proxied_class() + { + var interceptor = new WithCallbackInterceptor(invocation => + { + Assume.That(invocation.Method.Name == "ProtectedMethodWithDefaultImplementation"); + invocation.Proceed(); + }); + var proxy = generator.CreateClassProxy(interceptor); + var expected = "default implementation"; + var actual = ((IHaveProtectedMethodWithDefaultImplementation)proxy).InvokeProtectedMethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_protected_method_default_implementation_in_proxied_interface() + { + var interceptor = new WithCallbackInterceptor(invocation => + { + Assume.That(invocation.Method.Name == "ProtectedMethodWithDefaultImplementation"); + invocation.Proceed(); + }); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + var expected = "default implementation"; + var actual = proxy.InvokeProtectedMethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + #endregion + + #region Sealed members + [Test] public void Can_proxy_interface_with_sealed_method() { @@ -33,15 +381,171 @@ public void Can_proxy_interface_with_sealed_method() public void Can_invoke_sealed_method_in_proxied_interface() { var proxy = generator.CreateInterfaceProxyWithoutTarget(); - var invokedMethod = proxy.SealedMethod(); - Assert.AreEqual(typeof(IHaveSealedMethod).GetMethod(nameof(IHaveSealedMethod.SealedMethod)), invokedMethod); + var expected = "default implementation"; + var actual = proxy.SealedMethod(); + Assert.AreEqual(expected, actual); + } + + #endregion + + #region Static members + + [Test] + public void Can_proxy_interface_with_static_method() + { + _ = generator.CreateInterfaceProxyWithoutTarget(); + } + +#if NET7_0_OR_GREATER + [Test] + [Ignore("Support for static abstract interface members has not yet been implemented.")] + public void Can_proxy_interface_with_static_abstract_method() + { + _ = generator.CreateInterfaceProxyWithoutTarget(); + } +#endif + + #endregion + + #region Mixins + + [Test] + public void Can_intercept_method_with_default_implementation_from_mixin_in_proxied_class() + { + var options = new ProxyGenerationOptions(); + options.AddMixinInstance(new InheritsMethodWithDefaultImplementation()); + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateClassProxy(typeof(object), options, interceptor); + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_intercept_method_with_default_implementation_from_mixin_in_proxied_interface() + { + var options = new ProxyGenerationOptions(); + options.AddMixinInstance(new InheritsMethodWithDefaultImplementation()); + var expected = "intercepted"; + var interceptor = new WithCallbackInterceptor(invocation => invocation.ReturnValue = expected); + var proxy = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), options, interceptor); + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_method_default_implementation_from_mixin_in_proxied_class() + { + var options = new ProxyGenerationOptions(); + options.AddMixinInstance(new InheritsMethodWithDefaultImplementation()); + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateClassProxy(typeof(object), options, interceptor); + var expected = "default implementation"; + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + + [Test] + public void Can_proceed_to_method_default_implementation_from_mixin_in_proxied_interface() + { + var options = new ProxyGenerationOptions(); + options.AddMixinInstance(new InheritsMethodWithDefaultImplementation()); + var interceptor = new WithCallbackInterceptor(invocation => invocation.Proceed()); + var proxy = generator.CreateInterfaceProxyWithoutTarget(typeof(IEmpty), options, interceptor); + var expected = "default implementation"; + var actual = ((IHaveMethodWithDefaultImplementation)proxy).MethodWithDefaultImplementation(); + Assert.AreEqual(expected, actual); + } + #endregion + + public interface IHaveGenericMethodWithDefaultImplementation + { + string GenericMethodWithDefaultImplementation() + { + return "default implementation for " + typeof(T).Name; + } + } + + public interface IHaveMethodWithDefaultImplementation + { + string MethodWithDefaultImplementation() + { + return "default implementation"; + } + } + + public interface IHaveMethodWithDefaultImplementationAmongOtherThings : IHaveMethodWithDefaultImplementation + { + string Method(); + } + + public interface IHaveProtectedMethodWithDefaultImplementation + { + sealed string InvokeProtectedMethodWithDefaultImplementation() + { + return ProtectedMethodWithDefaultImplementation(); + } + + protected string ProtectedMethodWithDefaultImplementation() + { + return "default implementation"; + } + } + + public interface IHavePropertyWithDefaultImplementation + { + string PropertyWithDefaultImplementation + { + get + { + return "default implementation"; + } + } } public interface IHaveSealedMethod { - sealed MethodBase SealedMethod() + sealed string SealedMethod() + { + return "default implementation"; + } + } + + public interface IHaveStaticMethod + { + static string StaticMethod() + { + return "default implementation"; + } + } + +#if NET7_0_OR_GREATER + public interface IHaveStaticAbstractMethod + { + static abstract string StaticAbstractMethod(); + } +#endif + + public class InheritsMethodWithDefaultImplementation : IHaveMethodWithDefaultImplementation { } + + public class InheritsMethodWithDefaultImplementationAmongOtherThings : IHaveMethodWithDefaultImplementationAmongOtherThings + { + public virtual string Method() + { + return "class implementation"; + } + } + + public class InheritsProtectedMethodWithDefaultImplementation : IHaveProtectedMethodWithDefaultImplementation { } + + + public class InheritsPropertyWithDefaultImplementation : IHavePropertyWithDefaultImplementation { } + + public class OverridesMethodWithDefaultImplementation : IHaveMethodWithDefaultImplementation + { + public virtual string MethodWithDefaultImplementation() { - return MethodBase.GetCurrentMethod(); + return "class implementation"; } } } diff --git a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs index a7621b8c53..fb376b025e 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs @@ -42,6 +42,16 @@ protected override IEnumerable GetCollectors() var targetItem = new ClassMembersCollector(targetType) { Logger = Logger }; yield return targetItem; + foreach (var @interface in targetType.GetAllInterfaces()) + { + if (@interface.HasAnyOverridableDefaultImplementations() == false) + { + continue; + } + + yield return new InterfaceMembersWithDefaultImplementationCollector(@interface, targetType); + } + foreach (var @interface in interfaces) { var item = new InterfaceMembersOnClassCollector(@interface, true, diff --git a/src/Castle.Core/DynamicProxy/Contributors/InterfaceMembersCollector.cs b/src/Castle.Core/DynamicProxy/Contributors/InterfaceMembersCollector.cs index 81d998dbd0..ba11dd0370 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/InterfaceMembersCollector.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/InterfaceMembersCollector.cs @@ -35,7 +35,7 @@ protected override MetaMethod GetMethodToGenerate(MethodInfo method, IProxyGener return null; } - return new MetaMethod(method, method, isStandalone, proxyable, false); + return new MetaMethod(method, method, isStandalone, proxyable, hasTarget: !method.IsAbstract); } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Contributors/InterfaceMembersWithDefaultImplementationCollector.cs b/src/Castle.Core/DynamicProxy/Contributors/InterfaceMembersWithDefaultImplementationCollector.cs new file mode 100644 index 0000000000..70293a5c9b --- /dev/null +++ b/src/Castle.Core/DynamicProxy/Contributors/InterfaceMembersWithDefaultImplementationCollector.cs @@ -0,0 +1,71 @@ +// Copyright 2004-2023 Castle Project - http://www.castleproject.org/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Castle.DynamicProxy.Contributors +{ + using System; + using System.Diagnostics; + using System.Reflection; + + using Castle.DynamicProxy.Generators; + + internal sealed class InterfaceMembersWithDefaultImplementationCollector : MembersCollector + { + private readonly InterfaceMapping map; + + public InterfaceMembersWithDefaultImplementationCollector(Type interfaceType, Type classToProxy) + : base(interfaceType) + { + Debug.Assert(interfaceType != null); + Debug.Assert(interfaceType.IsInterface); + + Debug.Assert(classToProxy != null); + Debug.Assert(classToProxy.IsClass); + + map = classToProxy.GetInterfaceMap(interfaceType); + } + + protected override MetaMethod GetMethodToGenerate(MethodInfo method, IProxyGenerationHook hook, bool isStandalone) + { + if (method.IsAbstract || method.IsFinal || method.IsVirtual == false) + { + // This collector is only interested in overridable methods with default implementations. + // All other interface methods should be dealt with in other contributors. + // + // (Note this is the opposite check of that in `TypeUtil.HasAnyOverridableDefaultImplementations`, + // which is the method used to filter out whole interfaces before this collector gets run for them.) + return null; + } + + var index = Array.IndexOf(map.InterfaceMethods, method); + Debug.Assert(index >= 0); + + var methodOnTarget = map.TargetMethods[index]; + if (methodOnTarget.DeclaringType.IsInterface == false) + { + // An interface method can have its default implementation "overridden" in the class, + // in which case this collector isn't interested in it and another should deal with it. + return null; + } + + var proxyable = AcceptMethod(method, true, hook); + if (!proxyable) + { + return null; + } + + return new MetaMethod(method, methodOnTarget, isStandalone, proxyable, !method.IsAbstract); + } + } +} diff --git a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs index 739abac32b..d450b39c4b 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs @@ -17,9 +17,11 @@ namespace Castle.DynamicProxy.Contributors using System; using System.Collections.Generic; using System.Diagnostics; + using System.Reflection; using Castle.DynamicProxy.Generators; using Castle.DynamicProxy.Generators.Emitters; + using Castle.DynamicProxy.Generators.Emitters.SimpleAST; using Castle.DynamicProxy.Internal; internal class InterfaceProxyWithoutTargetContributor : CompositeTypeContributor @@ -63,23 +65,32 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter) { var methodInfo = method.Method; - if (canChangeTarget == false && methodInfo.IsAbstract) + if (canChangeTarget == false) { - // We do not need to generate a custom invocation type because no custom implementation - // for `InvokeMethodOnTarget` will be needed (proceeding to target isn't possible here): - return typeof(InterfaceMethodWithoutTargetInvocation); + if (!method.HasTarget) + { + // We do not need to generate a custom invocation type because no custom implementation + // for `InvokeMethodOnTarget` will be needed (proceeding to target isn't possible here): + return typeof(InterfaceMethodWithoutTargetInvocation); + } + else + { + // We end up here for interface methods with a default implementation: + Debug.Assert(methodInfo.DeclaringType.IsInterface && methodInfo.IsAbstract == false); + + // This allows proceeding to a interface method's default implementation. + // The code has been copied over from `ClassProxyTargetContributor`. + var callback = CreateCallbackMethod(emitter, methodInfo, method.MethodOnTarget); + return new InheritanceInvocationTypeGenerator(callback.DeclaringType, method, callback, null) + .Generate(emitter, namingScope) + .BuildType(); + } } + Debug.Assert(canChangeTarget); + var scope = emitter.ModuleScope; - Type[] invocationInterfaces; - if (canChangeTarget) - { - invocationInterfaces = new[] { typeof(IInvocation), typeof(IChangeProxyTarget) }; - } - else - { - invocationInterfaces = new[] { typeof(IInvocation) }; - } + Type[] invocationInterfaces = new[] { typeof(IInvocation), typeof(IChangeProxyTarget) }; var key = new CacheKey(methodInfo, CompositionInvocationTypeGenerator.BaseType, invocationInterfaces, null); // no locking required as we're already within a lock @@ -93,5 +104,24 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter) .Generate(emitter, namingScope) .BuildType()); } + + private MethodInfo CreateCallbackMethod(ClassEmitter emitter, MethodInfo methodInfo, MethodInfo methodOnTarget) + { + var targetMethod = methodOnTarget ?? methodInfo; + var callBackMethod = emitter.CreateMethod(namingScope.GetUniqueName(methodInfo.Name + "_callback"), targetMethod); + + if (targetMethod.IsGenericMethod) + { + targetMethod = targetMethod.MakeGenericMethod(callBackMethod.GenericTypeParams.AsTypeArray()); + } + + // invocation on base interface + + callBackMethod.CodeBuilder.AddStatement( + new ReturnStatement( + new MethodInvocationExpression(SelfReference.Self, targetMethod, callBackMethod.Arguments))); + + return callBackMethod.MethodBuilder; + } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs b/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs index 1074b69bbc..4abb205cd5 100644 --- a/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs +++ b/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs @@ -27,6 +27,7 @@ namespace Castle.DynamicProxy.Internal public static class TypeUtil { private static readonly Dictionary instanceMethodsCache = new Dictionary(); + private static readonly Dictionary hasAnyOverridableDefaultImplementationsCache = new Dictionary(); internal static bool IsNullableType(this Type type) { @@ -134,6 +135,32 @@ public static Type GetTypeOrNull(object target) return target.GetType(); } + internal static bool HasAnyOverridableDefaultImplementations(this Type interfaceType) + { + Debug.Assert(interfaceType != null); + Debug.Assert(interfaceType.IsInterface); + + var cache = hasAnyOverridableDefaultImplementationsCache; + lock (cache) + { + if (!cache.TryGetValue(interfaceType, out var result)) + { + foreach (var method in interfaceType.GetAllInstanceMethods()) + { + if (method.IsAbstract == false && method.IsFinal == false && method.IsVirtual) + { + result = true; + break; + } + } + + cache[interfaceType] = result; + } + + return result; + } + } + internal static Type[] AsTypeArray(this GenericTypeParameterBuilder[] typeInfos) { Type[] types = new Type[typeInfos.Length];