From 0c8f9d0d237ae2bc25c325fce2da562c467ca313 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Tue, 2 Feb 2021 00:59:53 +0100 Subject: [PATCH] Avoid unneeded custom invocation types on interface proxy w/o target --- .../InterfaceProxyWithoutTargetContributor.cs | 16 ++++- .../InterfaceMethodWithoutTargetInvocation.cs | 61 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/Castle.Core/DynamicProxy/Internal/InterfaceMethodWithoutTargetInvocation.cs diff --git a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs index cb1613cd13..d23164bb74 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs @@ -20,6 +20,7 @@ namespace Castle.DynamicProxy.Contributors using Castle.DynamicProxy.Generators; using Castle.DynamicProxy.Generators.Emitters; + using Castle.DynamicProxy.Internal; internal class InterfaceProxyWithoutTargetContributor : CompositeTypeContributor { @@ -60,6 +61,15 @@ protected override MethodGenerator GetMethodGenerator(MetaMethod method, ClassEm private Type GetInvocationType(MetaMethod method, ClassEmitter emitter) { + var methodInfo = method.Method; + + if (canChangeTarget == false && methodInfo.IsAbstract && methodInfo.IsGenericMethod == false && methodInfo.IsGenericMethodDefinition == 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); + } + var scope = emitter.ModuleScope; Type[] invocationInterfaces; if (canChangeTarget) @@ -70,14 +80,14 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter) { invocationInterfaces = new[] { typeof(IInvocation) }; } - var key = new CacheKey(method.Method, CompositionInvocationTypeGenerator.BaseType, invocationInterfaces, null); + var key = new CacheKey(methodInfo, CompositionInvocationTypeGenerator.BaseType, invocationInterfaces, null); // no locking required as we're already within a lock return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ => - new CompositionInvocationTypeGenerator(method.Method.DeclaringType, + new CompositionInvocationTypeGenerator(methodInfo.DeclaringType, method, - method.Method, + methodInfo, canChangeTarget, null) .Generate(emitter, namingScope) diff --git a/src/Castle.Core/DynamicProxy/Internal/InterfaceMethodWithoutTargetInvocation.cs b/src/Castle.Core/DynamicProxy/Internal/InterfaceMethodWithoutTargetInvocation.cs new file mode 100644 index 0000000000..a1c92a2dc9 --- /dev/null +++ b/src/Castle.Core/DynamicProxy/Internal/InterfaceMethodWithoutTargetInvocation.cs @@ -0,0 +1,61 @@ +// 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 InterfaceMethodWithoutTargetInvocation : AbstractInvocation + { + public InterfaceMethodWithoutTargetInvocation(object target, object proxy, IInterceptor[] interceptors, MethodInfo proxiedMethod, object[] arguments) + : base(proxy, interceptors, proxiedMethod, arguments) + { + // This invocation type is suitable for interface method invocations that cannot proceed + // to a target, i.e. where `InvokeMethodOnTarget` will always throw: + + Debug.Assert(target == null, $"{nameof(InterfaceMethodWithoutTargetInvocation)} does not support targets."); + Debug.Assert(proxiedMethod.IsAbstract, $"{nameof(InterfaceMethodWithoutTargetInvocation)} does not support non-abstract methods."); + + // Why this restriction? Because it greatly benefits proxy type generation performance. + // + // For invocations that can proceed to a target, `InvokeMethodOnTarget`'s implementation + // depends on the target method's signature. Because of this, DynamicProxy needs to + // dynamically generate a separate invocation type per such method. Type generation is + // always expensive... that is, slow. + // + // However, if it is known that `InvokeMethodOnTarget` won't forward, but throw, + // no custom (dynamically generated) invocation type is needed at all, and we can use + // this unspecific invocation type instead. + } + + // The next three properties mimick the behavior seen with an interface proxy without target. + // (This is why this type's name starts with `Interface`.) A similar type could be written + // for class proxies without target, but the values returned here would be different. + + public override object InvocationTarget => null; + + public override MethodInfo MethodInvocationTarget => null; + + public override Type TargetType => null; + + protected override void InvokeMethodOnTarget() => ThrowOnNoTarget(); + } +}