diff --git a/CHANGELOG.md b/CHANGELOG.md index 28b29ed756..f4cec7dcd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +Enhancements: +- Performance improvement with proxy type generation for class proxies (without target). Abstract class methods now reuse a predefined invocation type (like methods of interface proxies without target; see explanation below at version 5.0.0 enhancements) (@stakx, #625) + Bugfixes: - DynamicProxy emits invalid metadata for redeclared event (@stakx, #590) diff --git a/ref/Castle.Core-net462.cs b/ref/Castle.Core-net462.cs index e74ecc483d..281a4a0d7a 100644 --- a/ref/Castle.Core-net462.cs +++ b/ref/Castle.Core-net462.cs @@ -2788,6 +2788,12 @@ protected InheritanceInvocation(System.Type targetType, object proxy, Castle.Dyn protected abstract override void InvokeMethodOnTarget() { } } [System.Serializable] + public sealed class InheritanceInvocationWithoutTarget : Castle.DynamicProxy.Internal.InheritanceInvocation + { + public InheritanceInvocationWithoutTarget(System.Type targetType, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { } + protected override void InvokeMethodOnTarget() { } + } + [System.Serializable] public sealed class InterfaceMethodWithoutTargetInvocation : Castle.DynamicProxy.AbstractInvocation { public InterfaceMethodWithoutTargetInvocation(object target, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { } diff --git a/ref/Castle.Core-net6.0.cs b/ref/Castle.Core-net6.0.cs index 19326e29b9..b17ee6ead6 100644 --- a/ref/Castle.Core-net6.0.cs +++ b/ref/Castle.Core-net6.0.cs @@ -2742,6 +2742,11 @@ protected InheritanceInvocation(System.Type targetType, object proxy, Castle.Dyn public override System.Type TargetType { get; } protected abstract override void InvokeMethodOnTarget() { } } + public sealed class InheritanceInvocationWithoutTarget : Castle.DynamicProxy.Internal.InheritanceInvocation + { + public InheritanceInvocationWithoutTarget(System.Type targetType, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { } + protected override void InvokeMethodOnTarget() { } + } public sealed class InterfaceMethodWithoutTargetInvocation : Castle.DynamicProxy.AbstractInvocation { public InterfaceMethodWithoutTargetInvocation(object target, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { } diff --git a/ref/Castle.Core-netstandard2.0.cs b/ref/Castle.Core-netstandard2.0.cs index e2d8604e8f..4a95c5b07f 100644 --- a/ref/Castle.Core-netstandard2.0.cs +++ b/ref/Castle.Core-netstandard2.0.cs @@ -2740,6 +2740,11 @@ protected InheritanceInvocation(System.Type targetType, object proxy, Castle.Dyn public override System.Type TargetType { get; } protected abstract override void InvokeMethodOnTarget() { } } + public sealed class InheritanceInvocationWithoutTarget : Castle.DynamicProxy.Internal.InheritanceInvocation + { + public InheritanceInvocationWithoutTarget(System.Type targetType, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { } + protected override void InvokeMethodOnTarget() { } + } public sealed class InterfaceMethodWithoutTargetInvocation : Castle.DynamicProxy.AbstractInvocation { public InterfaceMethodWithoutTargetInvocation(object target, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { } diff --git a/ref/Castle.Core-netstandard2.1.cs b/ref/Castle.Core-netstandard2.1.cs index 00e2896aa3..a765438a83 100644 --- a/ref/Castle.Core-netstandard2.1.cs +++ b/ref/Castle.Core-netstandard2.1.cs @@ -2740,6 +2740,11 @@ protected InheritanceInvocation(System.Type targetType, object proxy, Castle.Dyn public override System.Type TargetType { get; } protected abstract override void InvokeMethodOnTarget() { } } + public sealed class InheritanceInvocationWithoutTarget : Castle.DynamicProxy.Internal.InheritanceInvocation + { + public InheritanceInvocationWithoutTarget(System.Type targetType, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { } + protected override void InvokeMethodOnTarget() { } + } public sealed class InterfaceMethodWithoutTargetInvocation : Castle.DynamicProxy.AbstractInvocation { public InterfaceMethodWithoutTargetInvocation(object target, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { } diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/InvocationTypeReuseTestCase.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/InvocationTypeReuseTestCase.cs index 21e79a9a6c..74fc257bdf 100644 --- a/src/Castle.Core.Tests/DynamicProxy.Tests/InvocationTypeReuseTestCase.cs +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/InvocationTypeReuseTestCase.cs @@ -52,6 +52,94 @@ public void Generic_method_of_interface_proxy_without_target__uses__InterfaceMet Assert.AreEqual(typeof(InterfaceMethodWithoutTargetInvocation), recorder.InvocationType); } + [Test] + public void Non_generic_abstract_method_of_class_proxy__uses__InheritanceInvocationWithoutTarget() + { + var recorder = new InvocationTypeRecorder(); + + var proxy = generator.CreateClassProxy(recorder); + proxy.Method(); + + Assert.AreEqual(typeof(InheritanceInvocationWithoutTarget), recorder.InvocationType); + } + + [Test] + public void Non_generic_protected_abstract_method_of_class_proxy__uses__InheritanceInvocationWithoutTarget() + { + var recorder = new InvocationTypeRecorder(); + + var proxy = generator.CreateClassProxy(recorder); + proxy.InvokeMethod(); + + Assert.AreEqual(typeof(InheritanceInvocationWithoutTarget), recorder.InvocationType); + } + + [Test] + public void Non_generic_virtual_method_of_class_proxy__does_not_use__InheritanceInvocationWithoutTarget() + { + var recorder = new InvocationTypeRecorder(); + + var proxy = generator.CreateClassProxy(recorder); + proxy.Method(); + + Assert.AreNotEqual(typeof(InheritanceInvocationWithoutTarget), recorder.InvocationType); + } + + [Test] + public void Non_generic_protected_virtual_method_of_class_proxy__does_not_use__InheritanceInvocationWithoutTarget() + { + var recorder = new InvocationTypeRecorder(); + + var proxy = generator.CreateClassProxy(recorder); + proxy.InvokeMethod(); + + Assert.AreNotEqual(typeof(InheritanceInvocationWithoutTarget), recorder.InvocationType); + } + + [Test] + public void Generic_abstract_method_of_class_proxy__uses__InheritanceInvocationWithoutTarget() + { + var recorder = new InvocationTypeRecorder(); + + var proxy = generator.CreateClassProxy(recorder); + proxy.Method(42); + + Assert.AreEqual(typeof(InheritanceInvocationWithoutTarget), recorder.InvocationType); + } + + [Test] + public void Generic_protected_abstract_method_of_class_proxy__uses__InheritanceInvocationWithoutTarget() + { + var recorder = new InvocationTypeRecorder(); + + var proxy = generator.CreateClassProxy(recorder); + proxy.InvokeMethod(42); + + Assert.AreEqual(typeof(InheritanceInvocationWithoutTarget), recorder.InvocationType); + } + + [Test] + public void Generic_virtual_method_of_class_proxy__does_not_use__InheritanceInvocationWithoutTarget() + { + var recorder = new InvocationTypeRecorder(); + + var proxy = generator.CreateClassProxy(recorder); + proxy.Method(42); + + Assert.AreNotEqual(typeof(InheritanceInvocationWithoutTarget), recorder.InvocationType); + } + + [Test] + public void Generic_protected_virtual_method_of_class_proxy__does_not_use__InheritanceInvocationWithoutTarget() + { + var recorder = new InvocationTypeRecorder(); + + var proxy = generator.CreateClassProxy(recorder); + proxy.InvokeMethod(42); + + Assert.AreNotEqual(typeof(InheritanceInvocationWithoutTarget), recorder.InvocationType); + } + public interface IWithNonGenericMethod { void Method(); @@ -62,6 +150,50 @@ public interface IWithGenericMethod void Method(T arg); } + public abstract class WithNonGenericAbstractMethod + { + public abstract void Method(); + } + + public abstract class WithNonGenericProtectedAbstractMethod + { + protected abstract void Method(); + public void InvokeMethod() => Method(); + } + + public class WithNonGenericVirtualMethod + { + public virtual void Method() { } + } + + public class WithNonGenericProtectedVirtualMethod + { + protected virtual void Method() { } + public void InvokeMethod() => Method(); + } + + public abstract class WithGenericAbstractMethod + { + public abstract void Method(T arg); + } + + public abstract class WithGenericProtectedAbstractMethod + { + protected abstract void Method(T arg); + public void InvokeMethod(T arg) => Method(arg); + } + + public class WithGenericVirtualMethod + { + public virtual void Method(T arg) { } + } + + public class WithGenericProtectedVirtualMethod + { + protected virtual void Method(T arg) { } + public void InvokeMethod(T arg) => Method(arg); + } + private sealed class InvocationTypeRecorder : IInterceptor { public Type InvocationType { get; private set; } diff --git a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs index fd2f3c49ce..a7621b8c53 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/ClassProxyTargetContributor.cs @@ -172,6 +172,13 @@ private Type GetDelegateType(MetaMethod method, ClassEmitter @class) private Type GetInvocationType(MetaMethod method, ClassEmitter @class) { + 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(InheritanceInvocationWithoutTarget); + } + // NOTE: No caching since invocation is tied to this specific proxy type via its invocation method return BuildInvocationType(method, @class); } diff --git a/src/Castle.Core/DynamicProxy/Internal/InheritanceInvocationWithoutTarget.cs b/src/Castle.Core/DynamicProxy/Internal/InheritanceInvocationWithoutTarget.cs new file mode 100644 index 0000000000..18c96895e8 --- /dev/null +++ b/src/Castle.Core/DynamicProxy/Internal/InheritanceInvocationWithoutTarget.cs @@ -0,0 +1,36 @@ +// Copyright 2004-2021 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.Internal +{ + using System; + using System.ComponentModel; + using System.Diagnostics; + using System.Reflection; + +#if FEATURE_SERIALIZATION + [Serializable] +#endif + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class InheritanceInvocationWithoutTarget : InheritanceInvocation + { + public InheritanceInvocationWithoutTarget(Type targetType, object proxy, IInterceptor[] interceptors, MethodInfo proxiedMethod, object[] arguments) + : base(targetType, proxy, interceptors, proxiedMethod, arguments) + { + Debug.Assert(proxiedMethod.IsAbstract, $"{nameof(InheritanceInvocationWithoutTarget)} does not support non-abstract methods."); + } + + protected override void InvokeMethodOnTarget() => ThrowOnNoTarget(); + } +}