-
Notifications
You must be signed in to change notification settings - Fork 484
Description
DynamicProxy currently emits one dedicated invocation type for each method that can proceed to a target method. Looking at C# 9's function pointers (delegate*) made me wonder if they could possibly replace those invocation types.
Case example
Say we have this type with a generic method that we want to proxy:
public class Foo
{
public virtual void Method<T2>(string arg1, T2 arg2) { }
}
var fooProxy = generator.CreateClassProxy<Foo>(...);Current code generation
Because Method<> is not abstract and can be proceeded to, DynamicProxy emits two additional things for it:
-
A method
FooProxy.Method_callbackthat calls the base method implementation:partial class FooProxy: Foo { public void Method_callback<T2>(string arg1, T2 arg2) { base.Method(arg1, arg2); } }
-
A
Foo_Method<>invocation type inheriting fromInheritanceInvocation, whoseInvokeMethodOnTargetmethod implementation delegates toFooProxy.Method_callback:public partial class Foo_Method<T2> : InheritanceInvocation { public override void InvokeMethodOnTarget() { ((FooProxy)this.proxyObject).Method_callback((string)this.Arguments[0], (T2)this.Arguments[1]); } }
Proposal: more efficient code generation using delegate*
What we could do instead is:
- Still emit
FooProxy.Method_callback, as is. - Instead of the
FooMethod<>invocation type, emit another methodFooProxy.Method_invokeOnTarget<>directly inside the proxy type. - Add a new pre-defined invocation type
Invocation(not its final name, obviously) to this library that will work for any proxied method – i. e. it might eventually even replace the existingCompositionInvocationandInheritanceInvocation.
Starting with (3), that general pre-defined invocation type might look as follows (uninteresting parts not shown):
public unsafe sealed class Invocation : IInvocation
{
private readonly delegate*<object, Invocation, void> invokeMethodOnTarget;
public Invocation(delegate*<object, Invocation, void> invokeMethodOnTarget, object proxy, object?[] arguments, ...)
{
this.invokeMethodOnTarget = invokeMethodOnTarget;
Proxy = proxy;
Arguments = arguments;
...
}
public object Proxy { get; }
public object?[] Arguments { get; }
private void InvokeMethodOnTarget()
{
if (invokeMethodOnTarget == null) throw new InvalidOperationException("There is no target to proceed to.");
invokeMethodOnTarget(Proxy!, this);
}
}That is, it receives a function pointer invokeMethodOnTarget that the invocation can use to proceed to the target method (whatever that may be).
Regarding (1) and (2), the emitted code for the Foo proxy type might look roughly as follows:
public unsafe class FooProxy : Foo
{
public override void Method<T2>(string arg1, T2 arg2)
{
new Invocation(&Method_invokeOnTarget<T2>, this, arguments: [arg1, arg2], ...).Proceed();
}
// DynamicProxy already emits this (albeit with `public` visibility, which would no longer be necessary):
private void Method_callback<T2>(string arg1, T2 arg2)
{
base.Method(arg1, arg2);
}
// This is the new method that would replace the separate `Foo_Method` invocation type:
private static void Method_invokeOnTarget<T>(object proxyObject, Invocation invocation)
{
((FooProxy)proxyObject).Method_callback((string)invocation.Arguments[0], (T2)invocation.Arguments[1]);
}
}Possible advantages
- Fewer types and method emitted would likely result in faster runtime performance during proxy type generation.
- Same amount of allocations and less virtual method dispatch might result in marginally better runtime performance during proxy object calls –
Invocation.InvokeMethodOnTargetdoes not need to be virtual, and could even be inlined. - Fewer pre-defined types needed in DynamicProxy – it's possible that the general-purpose
Invocationtype could replace all others (such asInheritanceInvocationandCompositionInvocation. - Possibly less complexity in generic type parameter handling. We don't need to transplant type parameters from a method to a class type; we'd only be emitting sibling methods next to the proxied method that have its exact same generic type parameters – this might possibly benefit Improve code generation performance/caching using open generic types #448.
This looks feasible to me, but I haven't done any deeper digging to see what problems might surface with this approach.