-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
Background and motivation
This will add "fast invoke" capability, allowing for up to 2x CPU improvement and eliminating the object[] parameters
alloc for some scenarios. The existing MethodBase.Invoke(...)
will also leverage this when possible. It is part of a larger goal to reduce usage of Reflection.Emit where feasible, which means performance is critical since Emit was originally used over Invoke because of performance.
API Proposal
New types:
namespace System.Reflection
{
public abstract class MethodBaseInvoker
{
internal MethodBaseInvoker(); // Or any internal ctor that removes the default ctor preventing external derivations.
}
public sealed class ConstructorInvoker : MethodBaseInvoker
{
// The caller is assumed to cache the invoker.
public static ConstructorInvoker Create(ConstructorInfo constructor);
internal ConstructorInvoker(); // Or any internal ctor that removes the default ctor preventing default constructor.
// All methods have the same semantics as 'MethodBase.Invoke(...)' for validation and defaulting with the
// possible exception (TBD) of supporting 'Type.Missing'; the user would need to use 'ParameterInfo.DefaultValue'.
// This supports copy-back for ref\out args like 'MethodBase.Invoke(...)'.
public object? Invoke(Span<object?> arguments);
public object? Invoke();
// These cannot support copy-back; adding 'ref' modifier is not desired here.
public object? Invoke(object? arg1);
public object? Invoke(object? arg1, object? arg2);
public object? Invoke(object? arg1, object? arg2, object? arg3);
public object? Invoke(object? arg1, object? arg2, object? arg3, object? arg4);
}
public sealed class MethodInvoker : MethodBaseInvoker
{
public static MethodInvoker Create(MethodInfo method);
internal MethodInvoker(); // Or any internal ctor that removes the default ctor preventing default constructor.
public object? Invoke(object? obj, Span<object?> arguments);
public object? Invoke(object? obj);
public object? Invoke(object? obj, object? arg1);
public object? Invoke(object? obj, object? arg1, object? arg2);
public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3);
public object? Invoke(object? obj, object? arg1, object? arg2, object? arg3, object? arg4);
}
}
API Usage
Simple replacement for MethodBase.Invoke:
MethodInfo method = typeof(TestClass).GetMethod(nameof(TestClass.Method1))!;
MethodInvoker cachedInvoker = MethodInvoker.Create(method);
TestClass obj = new();
// Invoke the method without having to create an `object[]` to contain the args.
cachedInvoker.Invoke(obj, 42);
public class TestClass
{
public void Method1(int i) { }
}
For high-performance APIs such as DI's ActivatorUtiliities.CreateFactory():
private static object CallConstructor(ConstructorInvoker cachedInvoker, Span<object> args)
{
// 'args' is not the exact set of parameters; we have "Transform()" logic to determine
if (args.Length <= 4)
{
// Fast logic to avoid object[] allocation
switch (args.Length)
{
case 0:
return cachedInvoker.Invoke();
case 1:
return cachedInvoker.Invoke(args[0].Transform());
case 2:
return cachedInvoker.Invoke(args[0].Transform(), args[1].Transform());
case 3:
return cachedInvoker.Invoke(args[0].Transform(), args[1].Transform(), args[2].Transform());
case 4:
return cachedInvoker.Invoke(args[0].Transform(), args[1].Transform(), args[2].Transform(), args[3].Transform());
}
}
else
{
Span<object> copyOfArgs = new object[args.Length];
for (int i = 0; i < args.Length; i++)
{
copyOfArgs[i] = args[i].Transform();
}
return cachedInvoker.Invoke(copyOfArgs);
}
}
private static object Transform(this object obj)
{
object newObj = // Assume special logic here such as defaulting or conversion of parameters.
return newObj;
}
Validation
Alternative Designs
Note that these proposed invoker classes already exist internally.
Support for byref-like types is covered here which has been prototyped successfully. However, the performance of that is not ideal when only System.Object
is necessary which is the case for DI's CreateFactory which calls constructors with user-specified object
- based args.