Skip to content

Commit

Permalink
Avoid unneeded custom invocation types on interface proxy w/o target
Browse files Browse the repository at this point in the history
  • Loading branch information
stakx committed Feb 3, 2021
1 parent c0fba5f commit 0c8f9d0
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 0c8f9d0

Please sign in to comment.