Skip to content

Commit 0c8f9d0

Browse files
committed
Avoid unneeded custom invocation types on interface proxy w/o target
1 parent c0fba5f commit 0c8f9d0

File tree

2 files changed

+74
-3
lines changed

2 files changed

+74
-3
lines changed

src/Castle.Core/DynamicProxy/Contributors/InterfaceProxyWithoutTargetContributor.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace Castle.DynamicProxy.Contributors
2020

2121
using Castle.DynamicProxy.Generators;
2222
using Castle.DynamicProxy.Generators.Emitters;
23+
using Castle.DynamicProxy.Internal;
2324

2425
internal class InterfaceProxyWithoutTargetContributor : CompositeTypeContributor
2526
{
@@ -60,6 +61,15 @@ protected override MethodGenerator GetMethodGenerator(MetaMethod method, ClassEm
6061

6162
private Type GetInvocationType(MetaMethod method, ClassEmitter emitter)
6263
{
64+
var methodInfo = method.Method;
65+
66+
if (canChangeTarget == false && methodInfo.IsAbstract && methodInfo.IsGenericMethod == false && methodInfo.IsGenericMethodDefinition == false)
67+
{
68+
// We do not need to generate a custom invocation type because no custom implementation
69+
// for `InvokeMethodOnTarget` will be needed (proceeding to target isn't possible here):
70+
return typeof(InterfaceMethodWithoutTargetInvocation);
71+
}
72+
6373
var scope = emitter.ModuleScope;
6474
Type[] invocationInterfaces;
6575
if (canChangeTarget)
@@ -70,14 +80,14 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter)
7080
{
7181
invocationInterfaces = new[] { typeof(IInvocation) };
7282
}
73-
var key = new CacheKey(method.Method, CompositionInvocationTypeGenerator.BaseType, invocationInterfaces, null);
83+
var key = new CacheKey(methodInfo, CompositionInvocationTypeGenerator.BaseType, invocationInterfaces, null);
7484

7585
// no locking required as we're already within a lock
7686

7787
return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ =>
78-
new CompositionInvocationTypeGenerator(method.Method.DeclaringType,
88+
new CompositionInvocationTypeGenerator(methodInfo.DeclaringType,
7989
method,
80-
method.Method,
90+
methodInfo,
8191
canChangeTarget,
8292
null)
8393
.Generate(emitter, namingScope)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Castle.DynamicProxy.Internal
16+
{
17+
using System;
18+
using System.ComponentModel;
19+
using System.Diagnostics;
20+
using System.Reflection;
21+
22+
#if FEATURE_SERIALIZATION
23+
[Serializable]
24+
#endif
25+
[EditorBrowsable(EditorBrowsableState.Never)]
26+
public sealed class InterfaceMethodWithoutTargetInvocation : AbstractInvocation
27+
{
28+
public InterfaceMethodWithoutTargetInvocation(object target, object proxy, IInterceptor[] interceptors, MethodInfo proxiedMethod, object[] arguments)
29+
: base(proxy, interceptors, proxiedMethod, arguments)
30+
{
31+
// This invocation type is suitable for interface method invocations that cannot proceed
32+
// to a target, i.e. where `InvokeMethodOnTarget` will always throw:
33+
34+
Debug.Assert(target == null, $"{nameof(InterfaceMethodWithoutTargetInvocation)} does not support targets.");
35+
Debug.Assert(proxiedMethod.IsAbstract, $"{nameof(InterfaceMethodWithoutTargetInvocation)} does not support non-abstract methods.");
36+
37+
// Why this restriction? Because it greatly benefits proxy type generation performance.
38+
//
39+
// For invocations that can proceed to a target, `InvokeMethodOnTarget`'s implementation
40+
// depends on the target method's signature. Because of this, DynamicProxy needs to
41+
// dynamically generate a separate invocation type per such method. Type generation is
42+
// always expensive... that is, slow.
43+
//
44+
// However, if it is known that `InvokeMethodOnTarget` won't forward, but throw,
45+
// no custom (dynamically generated) invocation type is needed at all, and we can use
46+
// this unspecific invocation type instead.
47+
}
48+
49+
// The next three properties mimick the behavior seen with an interface proxy without target.
50+
// (This is why this type's name starts with `Interface`.) A similar type could be written
51+
// for class proxies without target, but the values returned here would be different.
52+
53+
public override object InvocationTarget => null;
54+
55+
public override MethodInfo MethodInvocationTarget => null;
56+
57+
public override Type TargetType => null;
58+
59+
protected override void InvokeMethodOnTarget() => ThrowOnNoTarget();
60+
}
61+
}

0 commit comments

Comments
 (0)