Skip to content

[API Proposal]: Add invoker classes for fast invoke APIs #85539

@steveharter

Description

@steveharter

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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions